001/*
002 *  Copyright 2014 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.cdmfr;
017
018import java.text.SimpleDateFormat;
019import java.time.LocalDate;
020import java.util.Date;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Optional;
027import java.util.Set;
028
029import org.apache.avalon.framework.component.Component;
030import org.apache.avalon.framework.context.Context;
031import org.apache.avalon.framework.context.ContextException;
032import org.apache.avalon.framework.context.Contextualizable;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.cocoon.components.ContextHelper;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.xml.AttributesImpl;
039import org.apache.cocoon.xml.XMLUtils;
040import org.apache.commons.collections4.MapUtils;
041import org.apache.commons.lang3.ArrayUtils;
042import org.apache.commons.lang3.StringUtils;
043import org.apache.excalibur.source.SourceResolver;
044import org.xml.sax.ContentHandler;
045import org.xml.sax.SAXException;
046
047import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
048import org.ametys.cms.contenttype.ContentTypesHelper;
049import org.ametys.cms.data.ContentDataHelper;
050import org.ametys.cms.data.ContentValue;
051import org.ametys.cms.data.RichText;
052import org.ametys.cms.data.type.ModelItemTypeConstants;
053import org.ametys.cms.repository.Content;
054import org.ametys.cms.repository.ModifiableContent;
055import org.ametys.core.util.DateUtils;
056import org.ametys.odf.NoLiveVersionException;
057import org.ametys.odf.ODFHelper;
058import org.ametys.odf.course.Course;
059import org.ametys.odf.course.LOMSheet;
060import org.ametys.odf.courselist.CourseList;
061import org.ametys.odf.courselist.CourseList.ChoiceType;
062import org.ametys.odf.coursepart.CoursePart;
063import org.ametys.odf.enumeration.OdfReferenceTableEntry;
064import org.ametys.odf.enumeration.OdfReferenceTableHelper;
065import org.ametys.odf.orgunit.OrgUnit;
066import org.ametys.odf.orgunit.RootOrgUnitProvider;
067import org.ametys.odf.person.ContactData;
068import org.ametys.odf.person.Person;
069import org.ametys.odf.program.AbstractProgram;
070import org.ametys.odf.program.Container;
071import org.ametys.odf.program.Program;
072import org.ametys.odf.program.ProgramFactory;
073import org.ametys.odf.program.ProgramPart;
074import org.ametys.odf.program.SubProgram;
075import org.ametys.odf.program.TraversableProgramPart;
076import org.ametys.odf.program.WebsiteLink;
077import org.ametys.plugins.repository.AmetysObjectResolver;
078import org.ametys.plugins.repository.UnknownAmetysObjectException;
079import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
080import org.ametys.plugins.repository.model.RepositoryDataContext;
081import org.ametys.runtime.model.type.DataContext;
082import org.ametys.runtime.plugin.component.AbstractLogEnabled;
083
084/**
085 * Component able to generate CDM-fr for a {@link CDMEntity}.
086 */
087public class ExportCDMfrManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
088{
089    /** The Avalon role */
090    public static final String ROLE = ExportCDMfrManager.class.getName();
091    
092    /** Request attribute to get if the export is for Ametys application */
093    public static final String REQUEST_ATTRIBUTE_EXPORT_FOR_AMETYS = ExportCDMfrManager.class.getName() + "$forAmetys";
094    
095    private static final String[] __FIXED_DOMAIN_NAMES = new String[]{"ALL", "DEG", "SHS", "STS", "STA"};
096    
097    /** The Ametys object resolver */
098    protected AmetysObjectResolver _resolver;
099    /** The source resolver */
100    protected SourceResolver _sourceResolver;
101    /** The ODF enumeration helper */
102    protected OdfReferenceTableHelper _refTableHelper;
103    /** The root orgunit provider */
104    protected RootOrgUnitProvider _rootOrgUnitProvider;
105    /** The avalon context */
106    protected Context _context;
107    /** The CDMfr extension point */
108    protected CDMfrExtensionPoint _cdmFrExtensionPoint;
109    /** The content type extension point */
110    protected ContentTypeExtensionPoint _cTypeEP;
111    /** Helper for content types */
112    protected ContentTypesHelper _cTypeHelper;
113    /** The ODF helper */
114    protected ODFHelper _odfHelper;
115    
116    @Override
117    public void service(ServiceManager smanager) throws ServiceException
118    {
119        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
120        _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
121        _refTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE);
122        _rootOrgUnitProvider = (RootOrgUnitProvider) smanager.lookup(RootOrgUnitProvider.ROLE);
123        _cdmFrExtensionPoint = (CDMfrExtensionPoint) smanager.lookup(CDMfrExtensionPoint.ROLE);
124        _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
125        _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
126        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
127    }
128    
129    @Override
130    public void contextualize(Context context) throws ContextException
131    {
132        _context = context;
133    }
134    
135    /**
136     * Entry point to generate the CDM for a program
137     * @param contentHandler The content handler to sax into
138     * @param program The program
139     * @throws SAXException if failed to generate CDM
140     */
141    public void generateCDM(ContentHandler contentHandler, Program program) throws SAXException
142    {
143        contentHandler.startDocument();
144        contentHandler.startPrefixMapping("", "http://cdm-fr.fr/2013/CDM");
145        contentHandler.startPrefixMapping("xsi", "http://www.w3.org/2001/XMLSchema-instance");
146        contentHandler.startPrefixMapping("cdmfr", "http://cdm-fr.fr/2013/CDM-frSchema");
147        contentHandler.startPrefixMapping("xhtml", "http://www.w3.org/1999/xhtml");
148        contentHandler.startPrefixMapping("ametys-cdm", "http://www.ametys.org/cdm/1.0");
149            
150        AttributesImpl attrs = new AttributesImpl();
151        attrs.addCDATAAttribute("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", "xsi:schemaLocation", "http://cdm-fr.fr/2013/CDM http://cdm-fr.fr/2013/schemas/CDMFR.xsd");
152        attrs.addCDATAAttribute("profile", "CDM-fr");
153        attrs.addCDATAAttribute("language", program.getLanguage());
154        XMLUtils.startElement(contentHandler, "CDM", attrs);
155        
156        saxHabilitation(contentHandler, program);
157        
158        XMLUtils.startElement(contentHandler, "properties");
159        XMLUtils.createElement(contentHandler, "datasource", "Ametys v4.2");
160        attrs = new AttributesImpl();
161        attrs.addCDATAAttribute("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
162        XMLUtils.createElement(contentHandler, "datetime", attrs);
163        XMLUtils.endElement(contentHandler, "properties");
164        
165        program2CDM(contentHandler, program);
166        
167        XMLUtils.endElement(contentHandler, "CDM");
168        
169        contentHandler.endDocument();
170    }
171    
172    /**
173     * Switch to Live version if it was required
174     * @param ao the Ametys object
175     * @return true if the ametys object is switched
176     */
177    protected boolean switchToLiveVersionIfNeeded(DefaultAmetysObject ao)
178    {
179        try
180        {
181            _odfHelper.switchToLiveVersionIfNeeded(ao);
182            return true;
183        }
184        catch (NoLiveVersionException e)
185        {
186            getLogger().warn("Cannot export content to CDM : Live label is required but there is no Live version for content " + ao.getId());
187        }
188        
189        return false;
190    }
191    
192    /**
193     * Determines if this CDM export if for a Ametys application
194     * @return true if this CDM export if for a Ametys application
195     */
196    protected boolean isExportForAmetys()
197    {
198        Request request = ContextHelper.getRequest(_context);
199        return request.getAttribute(ExportCDMfrManager.REQUEST_ATTRIBUTE_EXPORT_FOR_AMETYS) != null;
200    }
201    
202    /**
203     * SAX CDMfr events for program's habilitation
204     * @param contentHandler the content handler to sax into
205     * @param program the program
206     * @throws SAXException if an error occurred
207     */
208    public void saxHabilitation(ContentHandler contentHandler, Program program) throws SAXException
209    {
210        /*
211         * <cdmfr:habilitation>
212         *      <cdmfr:habiliId>ID</cdmfr:habiliId>
213         *      <cdmfr:cohabilitation>
214         *          <cdmfr:exists>true|false</cdmfr:exists> // Habilité ou non
215         *          <cdmfr:listOfOrgUnit>
216         *              <cdmfr:habOrgUnit>
217         *                  <cdmfr:refOrgUnit> // Etablisssement cohabilité
218         *                  <cdmfr:domainName>
219         *                      <cdmfr:fixedDomain> // Domaine concerné par cette habilitation
220         *                  </cdmfr:domainName>
221         *              </cdmfr:habOrgUnit>
222         *          </cdmfr:listOfOrgUnit>
223         *      </cdmfr:cohabilitation>
224         *      <cdmfr:field>
225         *          <fieldname>
226         *              <free>MENTION</free>
227         *          </fieldname>
228         *          <speciality>
229         *              <specialityId/>
230         *              <specialityName/>
231         *          </speciality>
232         *      </cdmfr:field>
233         *      <cdmfr:partnership>
234         *          <cdmfr:training>
235         *              <cdmfr:trainingStrategy /> // Stages
236         *          </cdmfr:training>
237         *      </cdmfr:partnership>
238         * </cdmfr:habilitation>
239         * 
240         */
241        
242        if (program.isPublishable() && switchToLiveVersionIfNeeded(program))
243        {
244            Request request = ContextHelper.getRequest(_context);
245            Content currentContent = (Content) request.getAttribute(Content.class.getName());
246
247            try
248            {
249                request.setAttribute(Content.class.getName(), program);
250                
251                AttributesImpl attrs = new AttributesImpl();
252                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, "");
253                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_DIPLOMA_TYPE, getDiplomaType(program));
254                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_HABILITATION, attrs);
255                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_HABILITATION_ID, program.getCDMId());
256                
257                _saxCohabilitation(contentHandler, program);
258                _saxHabilitationJointOrgUnit(contentHandler, program);
259                _saxHabilitationDiploma(contentHandler, program);
260                _saxHabilitationPartnerShip(contentHandler, program);
261                
262                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_HABILITATION);
263            }
264            finally
265            {
266                request.setAttribute(Content.class.getName(), currentContent);
267            }
268        }
269    }
270    
271    /**
272     * Sax habilitation informations of the diploma.
273     * @param contentHandler The content handler
274     * @param program The program to sax
275     * @throws SAXException if an error occurs
276     */
277    protected void _saxHabilitationDiploma(ContentHandler contentHandler, Program program) throws SAXException
278    {
279        String speciality = program.getSpeciality();
280        String mention = program.getMention();
281        
282        if (StringUtils.isNotEmpty(mention) || StringUtils.isNotEmpty(speciality))
283        {
284            // Formation LMD : Mention - Speciality
285            /*
286             * <cdmfr:field>
287             *      <cdmfr:fieldName>
288             *          <cdmfr:controlled fieldNameCode="code_mention">
289             *              <cdmfr:registeredName>Libelle mention</cdmfr:registeredName>
290             *          </cdmfr:controlled>
291             *      </cdmfr:fieldName>
292             *      <cdmfr:speciality>
293             *          <cdmfr:specialityName/>
294             *          <cdmfr:specialityId/>
295             *          <cdmfr:refProgram/>
296             *      </cdmfr:speciality>
297             * </cdmfr:field>
298             */
299            
300            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "field");
301            _saxHabilitationMention(contentHandler, program, mention);
302            _saxHabilitationSpeciality(contentHandler, program, speciality);
303            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "field");
304        }
305        else if (OdfReferenceTableHelper.MENTION_LICENCEPRO.equals(_getMentionType(program)))
306        {
307            _saxHabilitationLicPro(contentHandler, program);
308        }
309        else
310        {
311            _saxHabilitationOtherDiploma(contentHandler, program);
312        }
313    }
314    
315    /**
316     * Sax habilitation informations of professional license.
317     * @param contentHandler The content handler
318     * @param program The program to sax
319     * @throws SAXException if an error occurs
320     */
321    protected void _saxHabilitationLicPro(ContentHandler contentHandler, Program program) throws SAXException
322    {
323        // Licence professionelle
324        /*
325         * <cdmfr:licPro>
326         *      <cdmfr:specialite>
327         *          <cdm:text/>
328         *      </cdmfr:specialite>
329         * </cdmfr:licPro>
330         */
331        
332        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "licPro");
333        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "specialite");
334        XMLUtils.createElement(contentHandler, "text", program.getSpeciality());
335        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "specialite");
336        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "licPro");
337    }
338
339    /**
340     * Sax habilitation informations for another diploma : no mention, not a license.
341     * @param contentHandler The content handler
342     * @param program The program to sax
343     * @throws SAXException if an error occurs
344     */
345    protected void _saxHabilitationOtherDiploma(ContentHandler contentHandler, Program program) throws SAXException
346    {
347        // Autre diplôme habilité ne s'inscrivant pas dans une structure mention-spécialité
348        /*
349         * <cdmfr:otherDiploma>
350         *      <cdmfr:refProgram/>
351         * </cdmfr:otherDiploma>
352         */
353        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "otherDiploma");
354        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "refProgram", program.getCDMId());
355        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "otherDiploma");
356    }
357
358    /**
359     * Sax habilitation informations of mention.
360     * @param contentHandler The content handler
361     * @param program The program to sax
362     * @param mention The mention to sax
363     * @throws SAXException if an error occurs
364     */
365    protected void _saxHabilitationMention(ContentHandler contentHandler, Program program, String mention) throws SAXException
366    {
367        if (StringUtils.isNotEmpty(mention))
368        {
369            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "fieldName");
370            
371            if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(program.getType("mention").getId()))
372            {
373                OdfReferenceTableEntry entry = _refTableHelper.getItem(mention);
374                
375                String mentionLabel = entry.getCdmValue();
376                if (StringUtils.isEmpty(mentionLabel))
377                {
378                    mentionLabel = entry.getLabel(program.getLanguage());
379                }
380                
381                AttributesImpl attrs = new AttributesImpl();
382                attrs.addCDATAAttribute("fieldNameCode", entry.getCode());
383                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "controlled", attrs);
384                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "registeredName", mentionLabel);
385                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "controlled");
386            }
387            else
388            {
389                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "free", mention);
390            }
391            
392            
393            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "fieldName");
394        }
395    }
396
397    /**
398     * Sax habilitation informations of speciality.
399     * @param contentHandler The content handler
400     * @param program The program to sax
401     * @param speciality The speciality to sax
402     * @throws SAXException if an error occurs
403     */
404    protected void _saxHabilitationSpeciality(ContentHandler contentHandler, Program program, String speciality) throws SAXException
405    {
406        if (StringUtils.isNotEmpty(speciality))
407        {
408            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "speciality");
409            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "specialityName", speciality);
410            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "specialityId", program.getCDMId() + "SP01"); // FIXME "SP + n° ordre de la spécialité"
411            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "refProgram", program.getCDMId());
412            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "speciality");
413        }
414    }
415
416    /**
417     * Sax cohabilitation informations.
418     * @param contentHandler The content handler
419     * @param program The program to sax
420     * @throws SAXException if an error occurs
421     */
422    protected void _saxCohabilitation(ContentHandler contentHandler, Program program) throws SAXException
423    {
424        String[] domains = program.getDomain();
425        boolean isHabilited = false;
426        for (String domain : domains)
427        {
428            isHabilited |= !"HD".equals(_refTableHelper.getItemCode(domain));
429        }
430        
431        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "cohabilitation");
432        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "exists", String.valueOf(isHabilited));
433        
434        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "listOfOrgUnit");
435        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "habOrgUnit");
436        
437        String rneCode = _getRootOrgUnitId(program);
438        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "refOrgUnit", rneCode);
439        
440        _saxHabilitationDomains(contentHandler, domains);
441        
442        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "habOrgUnit");
443        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "listOfOrgUnit");
444        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "cohabilitation");
445    }
446
447    /**
448     * Sax habilitation informations of domains.
449     * @param contentHandler The content handler
450     * @param domains The domains to SAX
451     * @throws SAXException if an error occurs
452     */
453    protected void _saxHabilitationDomains(ContentHandler contentHandler, String[] domains) throws SAXException
454    {
455        for (String domain : domains)
456        {
457            Content content = _resolver.resolveById(domain);
458            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
459            String domainName = entry.getCdmValue();
460            String domainCode = entry.getCode();
461            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "domainName");
462            if (ArrayUtils.contains(__FIXED_DOMAIN_NAMES, domainCode))
463            {
464                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "fixedDomain", domainName);
465            }
466            else
467            {
468                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "openDomain", domainName);
469            }
470            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "domainName");
471        }
472    }
473
474    /**
475     * Sax habilitation informations of joint orgunits.
476     * @param contentHandler The content handler
477     * @param program The program to sax
478     * @throws SAXException if an error occurs
479     */
480    protected void _saxHabilitationJointOrgUnit(ContentHandler contentHandler, Program program) throws SAXException
481    {
482        // Etablissement cohabilité
483        String[] jointOrgUnit = program.getJointOrgUnit();
484        if (jointOrgUnit.length > 0)
485        {
486            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "cohabilitation");
487            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "exists", String.valueOf(true));
488            
489            AttributesImpl attrs = new AttributesImpl();
490            attrs.addCDATAAttribute("cohabilitationRole", "cohabilité");
491            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "listOfOrgUnit", attrs);
492            for (String id : jointOrgUnit)
493            {
494                if (StringUtils.isNotEmpty(id))
495                {
496                    String uaiCode = _refTableHelper.getItemCode(id);
497                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "habOrgUnit");
498                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "refOrgUnit", uaiCode);
499                    String ouLabel = _refTableHelper.getItemLabel(id, program.getLanguage());
500                    if (ouLabel != null)
501                    {
502                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, ouLabel);
503                    }
504                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "habOrgUnit");
505                }
506            }
507            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "listOfOrgUnit");
508            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "cohabilitation");
509        }
510    }
511
512    /**
513     * Sax habilitation informations of partner ship.
514     * @param contentHandler The content handler
515     * @param program The program to sax
516     * @throws SAXException if an error occurs
517     */
518    protected void _saxHabilitationPartnerShip(ContentHandler contentHandler, Program program) throws SAXException
519    {
520        /*
521         * <cdmfr:partnership>
522         *     <cdmfr:training>
523         *          <cdmfr:trainingStrategy />
524         *     </cdmfr:training>
525         * </cdmfr:partnership>
526         */
527        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "partnership");
528        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "training");
529        DataContext richTextContext = RepositoryDataContext.newInstance()
530                                                           .withObject(program)
531                                                           .withDataPath(AbstractProgram.TRAINING_STRATEGY);
532        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "trainingStrategy", program.getTrainingStrategy(), richTextContext, _sourceResolver, false, isExportForAmetys());
533        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "training");
534        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + "partnership");
535    }
536    
537    /**
538     * Get the root organization unit uai code
539     * @param program the exported program
540     * @return the uai code of the root organization unit corresponding to the exported program
541     */
542    protected String _getRootOrgUnitId(Program program)
543    {
544        return _rootOrgUnitProvider.getRoot().getUAICode();
545    }
546    
547    /**
548     * Get the type of diploma
549     * @param program the program
550     * @return diploma type
551     */
552    protected String getDiplomaType (Program program)
553    {
554        String degreeMentionType = _getMentionType(program);
555        if (OdfReferenceTableHelper.MENTION_LICENCE.equals(degreeMentionType))
556        {
557            return "L";
558        }
559        else if (OdfReferenceTableHelper.MENTION_MASTER.equals(degreeMentionType))
560        {
561            return "M";
562        }
563        else if (OdfReferenceTableHelper.MENTION_LICENCEPRO.equals(degreeMentionType))
564        {
565            return "Lpro";
566        }
567        return "autre";
568    }
569
570    //***********************************************************************//
571    //                              PROGRAM                                  //
572    //***********************************************************************//
573    
574    /**
575     * Export this entity in CDM-fr format.
576     * @param contentHandler the target handler
577     * @param program the program
578     * @throws SAXException if an error occurs during export.
579     */
580    public void program2CDM(ContentHandler contentHandler, Program program) throws SAXException
581    {
582        boolean rootOrgUnitSaxed = false;
583        
584        Set<String> persons = new HashSet<>();
585        Set<String> orgUnits = new HashSet<>();
586        Set<String> coursesToSax = new HashSet<>();
587        
588        abstractProgram2CDM(contentHandler, program, persons, orgUnits, coursesToSax);
589        
590        Set<String> coursesAlreadySax = new HashSet<>();
591        
592        while (!coursesToSax.isEmpty())
593        {
594            Set<String> courses = new HashSet<>();
595            for (String courseId : coursesToSax)
596            {
597                coursesAlreadySax.add(courseId);
598                Course course = _resolver.resolveById(courseId);
599                courses.addAll(course2CDM(contentHandler, course, orgUnits, persons));
600            }
601            
602            coursesToSax.clear();
603            coursesToSax.addAll(courses);
604            coursesToSax.removeAll(coursesAlreadySax);
605        }
606
607        for (String orgUnitId : orgUnits)
608        {
609            OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
610            if (orgUnitId.equals(_rootOrgUnitProvider.getRootId()))
611            {
612                rootOrgUnitSaxed = true;
613            }
614            
615            _orgunit2CDM(contentHandler, orgUnit, persons);
616        }
617        
618        if (!rootOrgUnitSaxed)
619        {
620            _orgunit2CDM(contentHandler, _rootOrgUnitProvider.getRoot(), persons);
621        }
622        
623        for (String personId : persons)
624        {
625            Person person = _resolver.resolveById(personId);
626            person2CDM(contentHandler, person);
627        }
628    }
629    
630    /**
631     * Send the CDM-fr representation of their ProgramPart to the given Contenthandler.<br>
632     * Also collects referenced orgUnits, persons and courses, so that they could be sent afterwards.
633     * @param contentHandler the receiving contentHandler.
634     * @param program the program
635     * @param persons collected {@link Person} ids.
636     * @param orgUnits collected {@link OrgUnit} ids.
637     * @param courses collected {@link Course} ids.
638     * @throws SAXException if an error occurs during CDM processing.
639     */
640    public void abstractProgram2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, Set<String> persons, Set<String> orgUnits, Set<String> courses) throws SAXException
641    {
642        DataContext programRichTextsContext = RepositoryDataContext.newInstance()
643                                                                   .withObject(program);
644        if (program.isPublishable() && switchToLiveVersionIfNeeded(program))
645        {
646            Request request = ContextHelper.getRequest(_context);
647            Content currentContent = (Content) request.getAttribute(Content.class.getName());
648            
649            try
650            {
651                request.setAttribute(Content.class.getName(), program);
652                
653                String tagName = program.getCDMTagName();
654            
655                AttributesImpl attrs = new AttributesImpl();
656                
657                // <program id="FRUAI{rootOrgUnitId}PR{code}" language="...">
658                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, program.getCDMId());
659                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LANGUAGE, program.getLanguage());
660                XMLUtils.startElement(contentHandler, tagName, attrs);
661                
662                // <programID>code</programID>
663                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PROGRAM_ID, program.getCDMId());
664        
665                // <programName><text>name</text></programName>
666                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_NAME);
667                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_TEXT, program.getTitle());
668                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_NAME);
669                
670                _abstractProgram2CDMCode(contentHandler, program);
671                
672                _websiteLinks2CDM(contentHandler, program);
673                
674                // <programDescription>...</programDescription>
675                attrs = new AttributesImpl();
676                String programType = program.getEducationKind();
677                if (StringUtils.isNotEmpty(programType))
678                {
679                    programType = _refTableHelper.getItemCDMfrValue(programType, false);
680                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_NATURE, programType);
681                }
682                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_DESCRIPTION, attrs);
683                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getPresentation(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.PRESENTATION), _sourceResolver, false, isExportForAmetys());
684                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_DESCRIPTION);
685                
686                // <qualification>...</qualification>
687                _qualification2CDM(contentHandler, program, programRichTextsContext);
688                
689                // <levelCode @codeSet="bac|bac+1|bac+2|bac+3|..."/>
690                // <level @level="L|M|Doc"/>
691                _level2CDM(contentHandler, program);
692                
693                // <learningObjectives>...</learningObjectives>
694                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_LEARNING_OBJECTIVES, program.getObjectives(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.OBJECTIVES), _sourceResolver, false, isExportForAmetys());
695                
696                // <admissionInfo>
697                //      <cdmfr:admissionDescription>...</cdmfr:admissionDescription>
698                // </admissionInfo>
699                _admissionInfo2CDM(contentHandler, program, programRichTextsContext);
700                
701                // <recommendedPrerequisites>...</recommendedPrerequisites>
702                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_RECOMMANDED_PREREQUISITES, program.getRecommendedPrerequisite(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.RECOMMENDED_PREREQUISITE), _sourceResolver, false, isExportForAmetys());
703        
704                // <formalPrerequisites>...</formalPrerequisites>
705                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_FORMAL_PREREQUISITES, program.getNeededPrerequisite(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.NEEDED_PREREQUISITE), _sourceResolver, true, isExportForAmetys());
706                
707                // <teachingPlace>...</teachingPlace>
708                _place2CDM(contentHandler, program);
709                
710                // <targetGroup>...</targetGroup>
711                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_TARGET_GROUP, program.getTargetGroup(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.TARGET_GROUP), _sourceResolver, false, isExportForAmetys());
712        
713                // <formOfTeaching org="" method=""/>
714                String formOfTeachingMethod = _refTableHelper.getItemCDMfrValue(program.getDistanceLearning(), false);
715                for (String formOfTeachingOrg: program.getFormOfTeachingOrgs())
716                {
717                    attrs = new AttributesImpl();
718                    String formOfTeachingOrgValue = _refTableHelper.getItemCDMfrValue(formOfTeachingOrg, false);
719                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_METHOD, formOfTeachingMethod);
720                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_ORG, formOfTeachingOrgValue);
721                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_FORM_OF_TEACHING, attrs);
722                }
723    
724                // <instructionLanguage teachingLang="fr"/>
725                String[] langs = program.getEducationLanguage();
726                for (String lang : langs)
727                {
728                    String teachingLanguageValue = _refTableHelper.getItemCDMfrValue(lang, true);
729                    attrs = new AttributesImpl();
730                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_TEACHING_LANG, teachingLanguageValue);
731                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INSTRUCTION_LANGUAGE, attrs);
732                }
733                
734                // <studyAbroad>...</studoAbroad>
735                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_STUDY_ABROAD, program.getStudyAbroad(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.STUDY_ABROAD), _sourceResolver, false, isExportForAmetys());
736                
737                // <programDuration>...</programDuration>
738                String duration = program.getDuration();
739                if (StringUtils.isNotBlank(duration))
740                {
741                    String durationValue = _refTableHelper.getItemCDMfrValue(duration, true);
742                    if (durationValue != null)
743                    {
744                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PROGRAM_DURATION, durationValue);
745                    }
746                }
747                
748                // <cdmfr:programStructure>...</cdmfr:programStructure>
749                RichText teachingOrganization = program.getTeachingOrganization();
750                if (teachingOrganization != null && teachingOrganization.getLength() > 0)
751                {
752                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_STRUCTURE);
753                    CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getTeachingOrganization(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.TEACHING_ORGANIZATION), _sourceResolver, false, isExportForAmetys());
754                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_STRUCTURE);
755                }
756                
757                // Sax courses list
758                ContentValue[] children = program.getValueOrDefault(TraversableProgramPart.CHILD_PROGRAM_PARTS, new ContentValue[0]);
759                for (ContentValue child : children)
760                {
761                    Optional<ModifiableContent> content = child.getContentIfExists();
762                    if (content.isPresent() && content.get() instanceof CourseList courseList)
763                    {
764                        courselist2CDM(contentHandler, courseList, courses);
765                    }
766                }
767                
768                // <regulations/>
769                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REGULATIONS);
770        
771                // <expenses>...</expenses>
772                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_EXPENSES, program.getExpenses(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.EXPENSES), _sourceResolver, false, isExportForAmetys());
773                
774                // <universalAjustment>...</universalAjustment>
775                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_UNIVERSAL_ADJUSTMENT, program.getUniversalAdjustment(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.UNIVERSAL_ADJUSTMENT), _sourceResolver, false, isExportForAmetys());
776        
777                // <contacts>
778                //      <refPerson idRef="ID"/>
779                //      <refOrgUnit idRef="ID"/>
780                // </contacts>
781                _abstractProgram2CDMContacts(contentHandler, program, persons, orgUnits);
782                
783                // <infoBlock>...</infoBlock>
784                attrs = new AttributesImpl();
785                if (tagName.equals(CDMFRTagsConstants.TAG_SUB_PROGRAM))
786                {
787                    attrs.addCDATAAttribute("userDefined", "subProgram");
788                }
789                
790                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getAdditionalInformations(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.ADDITIONNAL_INFORMATIONS), _sourceResolver, false, isExportForAmetys(), attrs);
791        
792                // <infoBlock userDefined="ametys-extension>...</infoBlock>
793                saxExtensions(contentHandler, program, persons, orgUnits);
794                
795                // <programStructure>...</programStructure>
796                _programPart2CDMSubPrograms(contentHandler, program, persons, orgUnits, courses);
797                
798                // <searchword>keyword</searchword>
799                for (String keyword : program.getKeywords())
800                {
801                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_SEARCH_WORD, keyword);
802                }
803                
804                // </program>
805                XMLUtils.endElement(contentHandler, tagName);
806            }
807            finally
808            {
809                request.setAttribute(Content.class.getName(), currentContent);
810            }
811        }
812    }
813    
814    /**
815     * Export CDM extensions handling all elements that do not belong to the CDM-fr schema
816     * @param contentHandler the receiving contentHandler.
817     * @param content the current ODF Content
818     * @param persons collected {@link Person} ids.
819     * @param orgUnits collected {@link OrgUnit} ids.
820     * @throws SAXException if an error occurs during CDM processing.
821     */
822    protected void saxExtensions(ContentHandler contentHandler, Content content, Set<String> persons, Set<String> orgUnits) throws SAXException
823    {
824        AttributesImpl attrs = new AttributesImpl();
825        attrs.addCDATAAttribute("userDefined", "ametys-extension");
826        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs);
827        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_EXTENSION_BLOCK);
828        
829        for (String id : _cdmFrExtensionPoint.getExtensionsIds())
830        {
831            CDMfrExtension cdmFrExtension = _cdmFrExtensionPoint.getExtension(id);
832            
833            if (content instanceof AbstractProgram<?> abstractProgram)
834            {
835                if (abstractProgram instanceof Program program)
836                {
837                    cdmFrExtension.program2CDM(contentHandler, program, persons, orgUnits);
838                }
839                else if (abstractProgram instanceof SubProgram subProgram)
840                {
841                    cdmFrExtension.subProgram2CDM(contentHandler, subProgram, persons, orgUnits);
842                }
843                
844                cdmFrExtension.abstractProgram2CDM(contentHandler, abstractProgram, persons, orgUnits);
845            }
846            else if (content instanceof Container container)
847            {
848                cdmFrExtension.container2CDM(contentHandler, container, persons, orgUnits);
849            }
850            else if (content instanceof Course course)
851            {
852                cdmFrExtension.course2CDM(contentHandler, course, persons, orgUnits);
853            }
854            else if (content instanceof Person person)
855            {
856                cdmFrExtension.person2CDM(contentHandler, person);
857            }
858            else if (content instanceof OrgUnit orgunit)
859            {
860                cdmFrExtension.orgunit2CDM(contentHandler, orgunit);
861            }
862        }
863        
864        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_EXTENSION_BLOCK);
865        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK);
866    }
867    
868    /**
869     * SAX CDM-fr website links
870     * @param contentHandler the content handler to sax into
871     * @param program the program or subprogram
872     * @throws SAXException if an error occurred
873     */
874    protected void _websiteLinks2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
875    {
876        // <webLink>
877        //      <href>href</href>
878        //      <linkName>linkName</linkName>
879        // </webLink>
880        for (WebsiteLink website : program.getWebsiteLinks())
881        {
882            website.toCDM(contentHandler);
883        }
884    }
885
886    /**
887     * SAX CDM-fr teaching places
888     * @param contentHandler the content handler to sax into
889     * @param program the program or subprogram
890     * @throws SAXException if an error occurred
891     */
892    protected void _place2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
893    {
894        // <teachingPlace>
895        //      <cdmfr:refOrgUnit />
896        //      <adr>
897        //          <pcode></pcode>
898        //      </adr>
899        // </teachingPlace>
900        String[] places = program.getPlace();
901        for (String id : places)
902        {
903            if (StringUtils.isNotEmpty(id))
904            {
905                String postalCode = _refTableHelper.getItemCode(id);
906                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_PLACE);
907                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REF_ORG_UNIT);
908                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
909                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PCODE, postalCode);
910                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
911                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_PLACE);
912            }
913        }
914    }
915
916    /**
917     * SAX CDM-fr qualification
918     * @param contentHandler the content handler to sax into
919     * @param program the program or subprogram
920     * @param programRichTextsContext the context for program's rich texts
921     * @throws SAXException if an error occurred
922     */
923    protected void _qualification2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, DataContext programRichTextsContext) throws SAXException
924    {
925        // <qualification>
926        //      <qualificatioName><text></text></qualificationName>
927        //      <qualificationDescription>
928        //          <infoBlock>...</infoBlock>
929        //          <statistics>success rate</statistics>
930        //      </qualificationDescription>
931        //      <degree degree=""/>
932        //      <profession>...</profession>
933        //      <studyQualification>...</studyQualification>
934        // </qualification>
935        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION);
936        
937        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION_NAME);
938        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEXT);
939        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION_NAME);
940        
941        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_QUALIFICATION_DESCRIPTION);
942        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getQualification(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.QUALIFICATION), _sourceResolver, true, isExportForAmetys());
943        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_STATISTICS, program.getSuccessRate());
944        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_QUALIFICATION_DESCRIPTION);
945        
946        AttributesImpl attrs = new AttributesImpl();
947        String ects = _refTableHelper.getItemCDMfrValue(program.getEcts(), true);
948        _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_ECTS_CREDITS, ects);
949        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS, attrs);
950        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getKnowledgeCheck(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.KNOWLEDGE_CHECK), _sourceResolver, true, isExportForAmetys());
951        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS);
952
953        _degree2CDM(contentHandler, program);
954
955        // <profession>
956        //      <infoBlock>...</infoBlock>
957        //      <romeData romeCode="code" romeLibel=""/>
958        // </profession>
959        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROFESSION);
960        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getJobOpportunities(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.JOB_OPPORTUNITIES), _sourceResolver, true, isExportForAmetys());
961        _romeCodes2CDM(contentHandler, program);
962        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROFESSION);
963        
964        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_STUDY_QUALIFICATION);
965        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, program.getFurtherStudy(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.FURTHER_STUDY), _sourceResolver, true, isExportForAmetys());
966        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_COMPETENCE_DESC);
967        attrs = new AttributesImpl();
968        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIMITED, "false");
969        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_COMPETENCE_VALIDITY, attrs);
970        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_STUDY_QUALIFICATION);
971        
972        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION);
973    }
974    
975    /**
976     * SAX CDMfr degree
977     * @param contentHandler the content handler to sax into
978     * @param abstractProgram the program or subprogram
979     * @throws SAXException if an error occurred
980     */
981    protected void _degree2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> abstractProgram) throws SAXException
982    {
983        if (abstractProgram instanceof Program program)
984        {
985            AttributesImpl attrs = new AttributesImpl();
986            String degreeId = program.getDegree();
987            if (StringUtils.isNotEmpty(degreeId))
988            {
989                String cdmCode = _refTableHelper.getItemCDMfrValue(degreeId, false);
990                if (StringUtils.isNotEmpty(cdmCode))
991                {
992                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_DEGREE, cdmCode);
993                }
994                else
995                {
996                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_DEGREE_NOT_LMD, _refTableHelper.getItemCode(degreeId));
997                }
998                
999                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_DEGREE, attrs);
1000            }
1001        }
1002    }
1003
1004    /**
1005     * SAX CDMfr rome codes
1006     * @param contentHandler the content handler to sax into
1007     * @param program the program or subprogram
1008     * @throws SAXException if an error occurred
1009     */
1010    protected void _romeCodes2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
1011    {
1012        String[] romeCode = program.getRomeCode();
1013        for (String id : romeCode)
1014        {
1015            AttributesImpl attrs = new AttributesImpl();
1016            String code = _refTableHelper.getItemCode(id);
1017            attrs.addCDATAAttribute("romeCode", code);
1018            String label = _refTableHelper.getItemCDMfrValue(id, false);
1019            if (StringUtils.isNotBlank(label))
1020            {
1021                attrs.addCDATAAttribute("romeLibel", label);
1022            }
1023            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_ROME_DATA, attrs);
1024        }
1025    }
1026
1027    /**
1028     * SAX CDMfr level
1029     * @param contentHandler the content handler to sax into
1030     * @param program the program or subprogram
1031     * @throws SAXException if an error occurred
1032     */
1033    protected void _level2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
1034    {
1035        AttributesImpl attrs = new AttributesImpl();
1036        String educationLevel = program.getEducationLevel();
1037        _addNotNullAttribute(attrs, "codeSet", _refTableHelper.getItemCDMfrValue(educationLevel, true));
1038        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LEVEL_CODE, attrs);
1039        
1040        attrs = new AttributesImpl();
1041        String educationLevelCode = _refTableHelper.getItemCode(educationLevel);
1042        if (educationLevelCode.startsWith("licence"))
1043        {
1044            _addNotNullAttribute(attrs, "level", "L");
1045            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LEVEL, attrs);
1046        }
1047        else if (educationLevelCode.startsWith("master"))
1048        {
1049            _addNotNullAttribute(attrs, "level", "M");
1050            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LEVEL, attrs);
1051        }
1052        else if (educationLevelCode.startsWith("doctorat"))
1053        {
1054            _addNotNullAttribute(attrs, "level", "Doc");
1055            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LEVEL, attrs);
1056        }
1057    }
1058
1059    /**
1060     * SAX CDMfr admission info
1061     * @param contentHandler the content handler to sax into
1062     * @param program the program or subprogram
1063     * @param programRichTextsContext the context for program's rich texts
1064     * @throws SAXException if an error occurred
1065     */
1066    protected void _admissionInfo2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, DataContext programRichTextsContext) throws SAXException
1067    {
1068        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
1069        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_ADMISSION_DESCRIPTION, program.getAccessCondition(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.ACCESS_CONDITION), _sourceResolver, false, isExportForAmetys());
1070        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_STUDENT_STATUS, "");
1071        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_ECTS_REQUIRED, "");
1072        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_STUDENT_PLACES, program.getEffectives(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.NUMBER_OF_STUDENTS), _sourceResolver, false, isExportForAmetys());
1073        
1074        LocalDate teachingStart = program.getTeachingStart();
1075        if (teachingStart != null)
1076        {
1077            CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.TAG_TEACHING_START, teachingStart);
1078        }
1079        
1080        LocalDate registrationDeadline = program.getRegistrationDeadline();
1081        if (registrationDeadline != null)
1082        {
1083            CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.TAG_REGISTRATION_DEADLINE, registrationDeadline);
1084        }
1085        
1086        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REGISTRATION_DETAIL);
1087        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REGISTRATION_PROCESS);
1088        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REGISTRATION_MODALITIES, program.getInscription(), programRichTextsContext.cloneContext().withDataPath(AbstractProgram.INSCRIPTION), _sourceResolver, false, isExportForAmetys());
1089        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REGISTRATION_PROCESS);
1090        LocalDate registrationStart = program.getRegistrationStart();
1091        if (registrationStart != null)
1092        {
1093            CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.TAG_REGISTRATION_START, registrationStart);
1094        }
1095        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REGISTRATION_DETAIL);
1096        
1097        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
1098    }
1099    
1100    /**
1101     * SAX CDMfr events for a {@link AbstractProgram} codes
1102     * @param contentHandler the content handler to sax into
1103     * @param program the program or subprogram
1104     * @throws SAXException if an error occurred
1105     */
1106    protected void _abstractProgram2CDMCode(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
1107    {
1108        AttributesImpl attrs = new AttributesImpl();
1109        
1110        // <programCode codeSet="userDefined">code</programCode>
1111        String code = program.getCode();
1112        if (StringUtils.isNotBlank(code))
1113        {
1114            attrs = new AttributesImpl();
1115            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "userDefined");
1116            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, code);
1117        }
1118        
1119        // <programCode codeSet="codeCNIS-NSF">code</programCode>
1120        // <programCode libSet="codeCNIS-NSF">label</programCode>
1121        String nsfId = ContentDataHelper.getContentIdFromContentData(program, "nsfCode");
1122        if (StringUtils.isNotBlank(nsfId))
1123        {
1124            attrs = new AttributesImpl();
1125            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "codeCNIS-NSF");
1126            
1127            String nsfCode = _refTableHelper.getItemCode(nsfId);
1128            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, nsfCode);
1129            
1130            String codeLib = _refTableHelper.getItemCDMfrValue(nsfId, false);
1131            if (StringUtils.isNotEmpty(codeLib))
1132            {
1133                attrs = new AttributesImpl();
1134                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "codeCNIS-NSF");
1135                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1136            }
1137        }
1138        
1139        // <programCode codeSet="sectDGESIP">code</programCode>
1140        // <programCode libSet="sectDGESIP">label</programCode>
1141        for (String id : program.getDGESIPCode())
1142        {
1143            attrs = new AttributesImpl();
1144            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "sectDGESIP");
1145            
1146            String dgesipCode = _refTableHelper.getItemCode(id);
1147            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, dgesipCode);
1148            
1149            String codeLib = _refTableHelper.getItemCDMfrValue(id, false);
1150            if (StringUtils.isNotEmpty(codeLib))
1151            {
1152                attrs = new AttributesImpl();
1153                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "sectDGESIP");
1154                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1155            }
1156        }
1157        
1158        // <programCode codeSet="ERASMUS">code</programCode>
1159        // <programCode libSet="ERASMUS">label</programCode>
1160        for (String id : program.getErasmusCode())
1161        {
1162            attrs = new AttributesImpl();
1163            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "ERASMUS");
1164            
1165            String erasmusCode = _refTableHelper.getItemCode(id);
1166            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, erasmusCode);
1167            
1168            String codeLib = _refTableHelper.getItemCDMfrValue(id, false);
1169            if (StringUtils.isNotEmpty(codeLib))
1170            {
1171                attrs = new AttributesImpl();
1172                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "ERASMUS");
1173                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1174            }
1175        }
1176        
1177        // <programCode codeSet="cite97">code</programCode>
1178        // <programCode libSet="cite97">label</programCode>
1179        for (String id : program.getCite97Code())
1180        {
1181            attrs = new AttributesImpl();
1182            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "cite97");
1183            String cite97Code = _refTableHelper.getItemCode(id);
1184            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, cite97Code);
1185            
1186            String codeLib = _refTableHelper.getItemCDMfrValue(id, false);
1187            if (StringUtils.isNotEmpty(codeLib))
1188            {
1189                attrs = new AttributesImpl();
1190                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "cite97");
1191                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1192            }
1193        }
1194        
1195        // <programCode codeSet="FAP">code</programCode>
1196        // <programCode libSet="FAP">label</programCode>
1197        for (String id : program.getFapCode())
1198        {
1199            attrs = new AttributesImpl();
1200            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "FAP");
1201            String fapCode = _refTableHelper.getItemCode(id);
1202            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, fapCode);
1203            
1204            String codeLib = _refTableHelper.getItemCDMfrValue(id, false);
1205            if (StringUtils.isNotEmpty(codeLib))
1206            {
1207                attrs = new AttributesImpl();
1208                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "FAP");
1209                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1210            }
1211        }
1212        
1213        // <programCode codeSet="codeSectDiscipSISE">code</programCode>
1214        // <programCode libSet="codeSectDiscipSISE">label</programCode>
1215        for (String id : program.getSiseCode())
1216        {
1217            attrs = new AttributesImpl();
1218            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "sectDiscipSISE");
1219            
1220            String siseCode = _refTableHelper.getItemCode(id);
1221            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, siseCode);
1222            
1223            String codeLib = _refTableHelper.getItemCDMfrValue(id, false);
1224            if (StringUtils.isNotEmpty(codeLib))
1225            {
1226                attrs = new AttributesImpl();
1227                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LIB_SET, "sectDiscipSISE");
1228                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, codeLib);
1229            }
1230        }
1231    }
1232    
1233    /**
1234     * SAX CDMfr events for a {@link AbstractProgram} contacts
1235     * @param contentHandler the content handler to sax into
1236     * @param program the program or subprogram
1237     * @param persons the contacts
1238     * @param orgUnits the orgunits
1239     * @throws SAXException if an error occurred
1240     */
1241    protected void _abstractProgram2CDMContacts(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, Set<String> persons, Set<String> orgUnits) throws SAXException
1242    {
1243        AttributesImpl attrs = new AttributesImpl();
1244        
1245        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
1246        
1247        Map<String, List<String>> contactsByRole = program.getContactsByRole();
1248        
1249        for (Entry<String, List<String>> entry : contactsByRole.entrySet())
1250        {
1251            String role = entry.getKey();
1252            List<String> contactIds = entry.getValue();
1253            
1254            String roleCode = null;
1255            if (StringUtils.isNotEmpty(role))
1256            {
1257                roleCode = _refTableHelper.getItemCode(role);
1258            }
1259            
1260            for (String personID : contactIds)
1261            {
1262                try
1263                {
1264                    Person person = _resolver.resolveById(personID);
1265                    persons.add(person.getId());
1266                    attrs = new AttributesImpl();
1267                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, person.getCDMId());
1268                    if (roleCode != null)
1269                    {
1270                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ROLE, roleCode);
1271                    }
1272                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_PERSON, attrs);
1273                }
1274                catch (UnknownAmetysObjectException e)
1275                {
1276                    // Contact does exist anymore, ignore it
1277                }
1278            }
1279        }
1280
1281        for (String orgUnitId : program.getOrgUnits())
1282        {
1283            if (!"".equals(orgUnitId))
1284            {
1285                try
1286                {
1287                    OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
1288                    orgUnits.add(orgUnit.getId());
1289                    attrs = new AttributesImpl();
1290                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, orgUnit.getCDMId());
1291                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_ORG_UNIT, attrs);
1292                }
1293                catch (UnknownAmetysObjectException e)
1294                {
1295                    // OrgUnit does exist anymore, ignore it
1296                }
1297            }
1298        }
1299        
1300        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
1301    }
1302    
1303    /**
1304     * SAX CDMfr events for a {@link ProgramPart} strcuture
1305     * @param contentHandler the content handler to sax into
1306     * @param programPart the program part
1307     * @param persons the contacts
1308     * @param orgUnits the orgunits
1309     * @param courses the courses
1310     * @throws SAXException if an error occurred
1311     */
1312    protected void _programPart2CDMSubPrograms(ContentHandler contentHandler, TraversableProgramPart programPart, Set<String> persons, Set<String> orgUnits, Set<String> courses) throws SAXException
1313    {
1314        ContentValue[] children = programPart.getValueOrDefault(TraversableProgramPart.CHILD_PROGRAM_PARTS, new ContentValue[0]);
1315        for (ContentValue child : children)
1316        {
1317            Optional<ModifiableContent> content = child.getContentIfExists();
1318            if (content.isPresent())
1319            {
1320                if (content.get() instanceof AbstractProgram<?> program)
1321                {
1322                    abstractProgram2CDM(contentHandler, program, persons, orgUnits, courses);
1323                }
1324                else if (content.get() instanceof Container container)
1325                {
1326                    container2CDM(contentHandler, container, persons, orgUnits, courses);
1327                }
1328            }
1329        }
1330    }
1331    
1332    
1333    //***********************************************************************//
1334    //                             CONTAINER                                 //
1335    //***********************************************************************//
1336    
1337    /**
1338     * Send the CDM-fr representation of their ProgramPart to the given Contenthandler.<br>
1339     * Also collects referenced orgUnits, persons and courses, so that they could be sent afterwards.
1340     * @param contentHandler the receiving contentHandler.
1341     * @param container The container
1342     * @param persons collected {@link Person} ids.
1343     * @param orgUnits collected {@link OrgUnit} ids.
1344     * @param courses collected {@link Course} ids.
1345     * @throws SAXException if an error occurs during CDM processing.
1346     */
1347    public void container2CDM(ContentHandler contentHandler, Container container, Set<String> persons, Set<String> orgUnits, Set<String> courses) throws SAXException
1348    {
1349        if (container.isPublishable() && switchToLiveVersionIfNeeded(container))
1350        {
1351            Request request = ContextHelper.getRequest(_context);
1352            Content currentContent = (Content) request.getAttribute(Content.class.getName());
1353            
1354            try
1355            {
1356                request.setAttribute(Content.class.getName(), container);
1357                
1358                AttributesImpl attrs = new AttributesImpl();
1359                
1360                // <program id="FR_RNE_{rootOrgUnitId}_PR_{code}">
1361                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, container.getCDMId());
1362                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_SUB_PROGRAM, attrs);
1363                
1364                // <programID>code</programID>
1365                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PROGRAM_ID, container.getCDMId());
1366                
1367                // <programName><text>name</text></programName>
1368                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_NAME);
1369                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_TEXT, container.getTitle());
1370                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_NAME);
1371                
1372                // <programCode codeSet="userDefined">code</programCode>
1373                String code = container.getCode();
1374                if (StringUtils.isNotBlank(code))
1375                {
1376                    attrs = new AttributesImpl();
1377                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "userDefined");
1378                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_CODE, attrs, code);
1379                }
1380                
1381                // <programDescription>...</programDescription>
1382                attrs = new AttributesImpl();
1383                String nature = container.getNature();
1384                if (StringUtils.isNotEmpty(nature))
1385                {
1386                    nature = _refTableHelper.getItemCDMfrValue(nature, true);
1387                    _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_NATURE, nature);
1388                }
1389                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_DESCRIPTION, attrs);
1390                
1391                // <qualification>...</qualification>
1392                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION);
1393                _addPositiveAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_ECTS_CREDITS, container.getEcts());
1394                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS, attrs);
1395                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, null, null, null, true, isExportForAmetys());
1396                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS);
1397                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_QUALIFICATION);
1398                
1399                // <programStructure>...</programStructure>
1400                ContentValue[] children = container.getValueOrDefault(TraversableProgramPart.CHILD_PROGRAM_PARTS, new ContentValue[0]);
1401                for (ContentValue child : children)
1402                {
1403                    Optional<ModifiableContent> content = child.getContentIfExists();
1404                    if (content.isPresent() && content.get() instanceof CourseList courseList)
1405                    {
1406                        courselist2CDM(contentHandler, courseList, courses);
1407                    }
1408                }
1409                
1410                // <infoBlock>...</infoBlock>
1411                attrs = new AttributesImpl();
1412                attrs.addCDATAAttribute("userDefined", "container");
1413                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs);
1414                
1415                // <infoBlock userDefined="ametys-extension>...</infoBlock>
1416                saxExtensions(contentHandler, container, persons, orgUnits);
1417                
1418                // <subProgram>...</subProgram>
1419                _programPart2CDMSubPrograms(contentHandler, container, persons, orgUnits, courses);
1420                
1421                // </program>
1422                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_SUB_PROGRAM);
1423            }
1424            finally
1425            {
1426                request.setAttribute(Content.class.getName(), currentContent);
1427            }
1428        }
1429    }
1430    
1431    /**
1432     * SAX the course list structure
1433     * @param contentHandler The content handler to SAX into
1434     * @param cl The course list
1435     * @param courses References of saxed courses
1436     * @throws SAXException if an error occurs
1437     */
1438    public void courselist2CDM (ContentHandler contentHandler, CourseList cl, Set<String> courses) throws SAXException
1439    {
1440        if (cl.isPublishable() && switchToLiveVersionIfNeeded(cl))
1441        {
1442            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_STRUCTURE);
1443            
1444            _courselist2CDM (contentHandler, cl);
1445            
1446            for (Course course  : cl.getCourses())
1447            {
1448                if (course.isPublishable())
1449                {
1450                    courses.add(course.getId());
1451                    AttributesImpl attrs = new AttributesImpl();
1452                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, course.getCDMId());
1453                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REF_COURSE, attrs);
1454                }
1455            }
1456            
1457            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROGRAM_STRUCTURE);
1458        }
1459    }
1460    
1461    /**
1462     * Convert course list info to CDM (without child courses)
1463     * @param contentHandler The content handler to sax into
1464     * @param cl The course list
1465     * @throws SAXException if an error occurs
1466     */
1467    protected void _courselist2CDM (ContentHandler contentHandler, CourseList cl) throws SAXException
1468    {
1469        if (switchToLiveVersionIfNeeded(cl))
1470        {
1471            AttributesImpl attrs = new AttributesImpl();
1472            attrs.addCDATAAttribute("userDefined", "listCode");
1473            String code = cl.getCode();
1474            if (StringUtils.isEmpty(code))
1475            {
1476                code = org.ametys.core.util.StringUtils.generateKey().toUpperCase();
1477            }
1478            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs, code);
1479            
1480            attrs = new AttributesImpl();
1481            attrs.addCDATAAttribute("userDefined", "listName");
1482            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs, cl.getTitle());
1483            
1484            ChoiceType type = cl.getType();
1485            if (type != null)
1486            {
1487                attrs = new AttributesImpl();
1488                attrs.addCDATAAttribute("userDefined", "listChoiceType");
1489                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs, cl.getType().toString().toLowerCase());
1490            }
1491            
1492            if (type != null && type.equals(ChoiceType.CHOICE))
1493            {
1494                attrs = new AttributesImpl();
1495                attrs.addCDATAAttribute("userDefined", "listMinNumberOfCourses");
1496                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs, String.valueOf(cl.getMinNumberOfCourses()));
1497                
1498                attrs = new AttributesImpl();
1499                attrs.addCDATAAttribute("userDefined", "listMaxNumberOfCourses");
1500                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, attrs, String.valueOf(cl.getMaxNumberOfCourses()));
1501            }
1502        }
1503    }
1504    
1505    //***********************************************************************//
1506    //                               COURSE                                  //
1507    //***********************************************************************//
1508    
1509    /**
1510     * Export this entity in CDM-fr format.
1511     * @param contentHandler the target handler
1512     * @param course The course
1513     * @throws SAXException if an error occurs during export.
1514     */
1515    public void course2CDM(ContentHandler contentHandler, Course course) throws SAXException
1516    {
1517        boolean rootOrgUnitSaxed = false;
1518        
1519        Set<String> orgUnits = new HashSet<>();
1520        Set<String> persons = new HashSet<>();
1521        
1522        Set<String> coursesAlreadySax = new HashSet<>();
1523        Set<String> coursesToSax = new HashSet<>();
1524        coursesToSax.add(course.getId());
1525        
1526        while (!coursesToSax.isEmpty())
1527        {
1528            Set<String> courses = new HashSet<>();
1529            for (String courseId : coursesToSax)
1530            {
1531                coursesAlreadySax.add(courseId);
1532                Course courseToSax = _resolver.resolveById(courseId);
1533                courses.addAll(course2CDM(contentHandler, courseToSax, orgUnits, persons));
1534            }
1535            coursesToSax.clear();
1536            coursesToSax.addAll(courses);
1537            coursesToSax.removeAll(coursesAlreadySax);
1538        }
1539        
1540        for (String orgUnitId : orgUnits)
1541        {
1542            OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
1543            if (orgUnitId.equals(_rootOrgUnitProvider.getRootId()))
1544            {
1545                rootOrgUnitSaxed = true;
1546            }
1547            _orgunit2CDM(contentHandler, orgUnit, persons);
1548        }
1549        
1550        if (!rootOrgUnitSaxed)
1551        {
1552            OrgUnit orgUnit = _rootOrgUnitProvider.getRoot();
1553            _orgunit2CDM(contentHandler, orgUnit, persons);
1554        }
1555        
1556        for (String personId : persons)
1557        {
1558            Person person = _resolver.resolveById(personId);
1559            person2CDM(contentHandler, person);
1560        }
1561        
1562        // Set content in request
1563        ContextHelper.getRequest(_context).setAttribute(Content.class.getName(), course);
1564    }
1565    
1566    /**
1567     * Export this entity in CDM-fr format.
1568     * @param contentHandler the content handler to SAX into
1569     * @param course the course
1570     * @param orgUnits the org units
1571     * @param persons the persons
1572     * @return the saxed coursed
1573     * @throws SAXException if failed to generate CDM
1574     */
1575    public Set<String> course2CDM(ContentHandler contentHandler, Course course, Set<String> orgUnits, Set<String> persons) throws SAXException
1576    {
1577        DataContext courseRichTextsContext = RepositoryDataContext.newInstance()
1578                                                                  .withObject(course);
1579
1580        if (!course.isPublishable() || !switchToLiveVersionIfNeeded(course))
1581        {
1582            return new HashSet<>();
1583        }
1584        
1585        Request request = ContextHelper.getRequest(_context);
1586        Content currentContent = (Content) request.getAttribute(Content.class.getName());
1587        
1588        try
1589        {
1590            request.setAttribute(Content.class.getName(), course);
1591            
1592            AttributesImpl attrs = new AttributesImpl();
1593            
1594            // <course id="FR_RNE_{rootOrgUnitId}_CO_{code}" language="...">
1595            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, course.getCDMId());
1596            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_LANGUAGE, course.getLanguage());
1597            String erasmusCode = _refTableHelper.getItemCode(course.getErasmusCode());
1598            _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_IDENT, erasmusCode);
1599            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_COURSE, attrs);
1600            
1601            // <courseID>code</courseID>
1602            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_ID, course.getCDMId());
1603            
1604            // <courseName><text>name</text></courseName>
1605            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_NAME);
1606            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEXT, course.getTitle());
1607            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_NAME);
1608    
1609            // <courseCode codeSet="UE">code</couseCode>
1610            String courseType = _refTableHelper.getItemCode(course.getCourseType());
1611            attrs = new AttributesImpl();
1612            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, courseType);
1613            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_CODE, attrs, course.getCdmCode());
1614            
1615            // <webLink>
1616            //      <href>href</href>
1617            //      <linkName>linkName</linkName>
1618            // </webLink>
1619            String webLinkURL = course.getWebLinkUrl();
1620            if (StringUtils.isNotEmpty(webLinkURL))
1621            {
1622                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_WEB_LINK);
1623                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_HREF, webLinkURL);
1624                String webLinkLabel = course.getWebLinkLabel();
1625                if (StringUtils.isNotEmpty(webLinkLabel))
1626                {
1627                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_LINK_NAME, course.getWebLinkLabel());
1628                }
1629                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_WEB_LINK);
1630            }
1631    
1632            // <level>level</level>
1633            attrs = new AttributesImpl();
1634            String level = _refTableHelper.getItemCDMfrValue(course.getLevel(), true);
1635            _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_LEVEL, level);
1636            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LEVEL, attrs);
1637            
1638            // <courseDescription>...</courseDescription>
1639            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_DESCRIPTION);
1640            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_DIGITAL_USE);
1641            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PERCENTAGE, "0");
1642            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_LCMS);
1643            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_EXISTS, "false");
1644            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_RESOURCE_MGT);
1645            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PERC_OF_USE, "0");
1646            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_LCMS);
1647            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROD_PEDA);
1648            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_DIGIT_PROD_PERC, "0");
1649            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_DIGIT_PROD_UNT_PERC, "0");
1650            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROD_PEDA);
1651            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_DIGITAL_USE);
1652            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_INFO_BLOCK, course.getDescription(), courseRichTextsContext.cloneContext().withDataPath(Course.DESCRIPTION), _sourceResolver, true, isExportForAmetys());
1653            
1654            Set<String> courses = _courseLists2CDM(contentHandler, course);
1655            
1656            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_COURSE_DESCRIPTION);
1657            
1658            // <teachingTerm term="" timeOfDay="" start=""/>
1659            attrs = new AttributesImpl();
1660            LocalDate startDate = course.getStartDate();
1661            if (startDate != null)
1662            {
1663                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_START, startDate.format(CDMHelper.CDM_DATE_FORMATTER));
1664            }
1665            
1666            String teachingTerm = _refTableHelper.getItemCDMfrValue(course.getTeachingTerm(), true);
1667            _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_TERM, teachingTerm);
1668            String timeSlot = _refTableHelper.getItemCDMfrValue(course.getTimeSlot(), true);
1669            _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_TIME_OF_DAY, timeSlot);
1670            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_TERM, attrs);
1671    
1672            // <credits ECTScredits="" totalWorkLoad="">
1673            //      <infoBlock>...</infoBlock>
1674            //      <globalVolume teachingType="CM/TP/TD">...</globalVolume>
1675            // </credits>
1676            _credits2CDM(contentHandler, course);
1677            
1678            // <learningObjectives>...</learningObjectives>
1679            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_LEARNING_OBJECTIVES);
1680            CDMHelper.richText2CDM(contentHandler, course.getObjectives(), courseRichTextsContext.cloneContext().withDataPath(Course.OBJECTIVES), _sourceResolver, isExportForAmetys());
1681            // <ametys-cdm:skills>...</ametys-cdm:skills>
1682            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "skills", course.getSkills(), courseRichTextsContext.cloneContext().withDataPath(Course.SKILLS), _sourceResolver, false, isExportForAmetys());
1683            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_LEARNING_OBJECTIVES);
1684            
1685            // <admissionInfo>
1686            //      <studentPlaces places=""/>
1687            //      <teachingStart date=""/>
1688            // </admissionInfo>
1689            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
1690    
1691            String maxNumberOfStudents = course.getMaxNumberOfStudents();
1692            if (StringUtils.isNotEmpty(maxNumberOfStudents))
1693            {
1694                attrs = new AttributesImpl();
1695                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_PLACES, maxNumberOfStudents);
1696                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_STUDENT_PLACES, attrs);
1697            }
1698            
1699            if (startDate != null)
1700            {
1701                attrs = new AttributesImpl();
1702                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_DATE, new SimpleDateFormat("yyyy-MM-dd").format(DateUtils.asDate(startDate)));
1703                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_START, attrs);
1704            }
1705            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
1706            
1707            // <formalPrerequisites>...</formalPrerequisites>
1708            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_FORMAL_PREREQUISITES, course.getNeededPrerequisite(), courseRichTextsContext.cloneContext().withDataPath(Course.NEEDED_PREREQUISITE), _sourceResolver, true, isExportForAmetys());
1709    
1710            // <teachingPlace>
1711            //      <cdmfr:refOrgUnit />
1712            //      <adr>
1713            //          <pcode></pcode>
1714            //      </adr>
1715            // </teachingPlace>
1716            _teachingLocation2CDM(contentHandler, course);
1717            
1718            // <formOfTeaching method="" org=""/>
1719            String formOfTeachingMethod = _refTableHelper.getItemCDMfrValue(course.getFormOfTeachingMethod(), false);
1720            for (String formOfTeachingOrg: course.getFormOfTeachingOrgs())
1721            {
1722                attrs = new AttributesImpl();
1723                String formOfTeachingOrgValue = _refTableHelper.getItemCDMfrValue(formOfTeachingOrg, false);
1724                _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_METHOD, formOfTeachingMethod);
1725                _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_ORG, formOfTeachingOrgValue);
1726                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_FORM_OF_TEACHING, attrs);
1727            }
1728            
1729            // <formOfAssessment>...</formOfAssessment>
1730            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_FORM_OF_ASSESSMENT, course.getFormOfAssessment(), courseRichTextsContext.cloneContext().withDataPath(Course.FORM_OF_ASSESSMENT), _sourceResolver, false, isExportForAmetys());
1731            
1732            // <instructionLanguage teachingLang=""/>
1733            String[] teachingLanguages = course.getTeachingLanguage();
1734            for (String teachingLanguage : teachingLanguages)
1735            {
1736                String teachingLanguageValue = _refTableHelper.getItemCDMfrValue(teachingLanguage, true);
1737                attrs = new AttributesImpl();
1738                attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_TEACHING_LANG, teachingLanguageValue);
1739                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_INSTRUCTION_LANGUAGE, attrs);
1740            }
1741            
1742            // <syllabus>
1743            // ...
1744            //   <webLink role="learningObjectMetadata">
1745            //      <href>...</href>
1746            //      <linkName>...</linkName>
1747            //   </webLink>
1748            // </syllabus>
1749            _syllabus2CDM(contentHandler, course, courseRichTextsContext);
1750            
1751            // <teachingActivity method=""/>
1752            attrs = new AttributesImpl();
1753            String teachingActivity = _refTableHelper.getItemCDMfrValue(course.getTeachingActivity(), true);
1754            _addNotNullAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_METHOD, teachingActivity);
1755            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_ACTIVITY, attrs);
1756            
1757            // <contacts>
1758            //      <refPerson idRef=ID/>
1759            // </contacts>
1760            _course2CDMContacts(contentHandler, course, persons, orgUnits);
1761            
1762            // <infoBlock>...</infoBlock>
1763            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, course.getAdditionalInformations(), courseRichTextsContext.cloneContext().withDataPath(Course.ADDITIONAL_INFORMATIONS), _sourceResolver, false, isExportForAmetys());
1764            
1765            // <infoBlock userDefined="ametys-extension>...</infoBlock>
1766            saxExtensions(contentHandler, course, persons, orgUnits);
1767            
1768            // <searchword>keyword</searchword>
1769            for (String keyword : course.getKeywords())
1770            {
1771                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_SEARCH_WORD, keyword);
1772            }
1773           
1774            // </course>
1775            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_COURSE);
1776
1777            return courses;
1778        }
1779        finally
1780        {
1781            request.setAttribute(Content.class.getName(), currentContent);
1782        }
1783    }
1784    
1785    /**
1786     * Sax the courses lists for a course.
1787     * @param contentHandler the content handler to SAX into
1788     * @param course The courselist container.
1789     * @return The list of courses into the course list.
1790     * @throws SAXException if failed to generate CDM
1791     */
1792    protected Set<String> _courseLists2CDM(ContentHandler contentHandler, Course course) throws SAXException
1793    {
1794        Set<String> courses = new HashSet<>();
1795        
1796        for (CourseList coursesList : course.getCourseLists())
1797        {
1798            if (coursesList.isPublishable())
1799            {
1800                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_COURSE_CONTENTS);
1801                
1802                // SAX infos on course list. This DO NOT respect the CDM-fr schema
1803                _courselist2CDM(contentHandler, coursesList);
1804                
1805                for (Course childCourse : coursesList.getCourses())
1806                {
1807                    if (childCourse.isPublishable())
1808                    {
1809                        courses.add(childCourse.getId());
1810                        AttributesImpl attrs = new AttributesImpl();
1811                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, childCourse.getCDMId());
1812                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REF_COURSE, attrs);
1813                    }
1814                }
1815                
1816                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_COURSE_CONTENTS);
1817            }
1818        }
1819        
1820        return courses;
1821    }
1822    
1823    /**
1824     * Sax the course credits.
1825     * @param contentHandler the content handler to SAX into
1826     * @param course The course to sax
1827     * @throws SAXException if failed to generate CDM
1828     */
1829    protected void _credits2CDM(ContentHandler contentHandler, Course course) throws SAXException
1830    {
1831        AttributesImpl attrs = new AttributesImpl();
1832        _addPositiveAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_TOTAL_WORK_LOAD, course.getNumberOfHours());
1833        _addPositiveAttribute(attrs, CDMFRTagsConstants.ATTRIBUTE_ECTS_CREDITS, course.getEcts());
1834        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS, attrs);
1835        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, null, null, _sourceResolver, true, isExportForAmetys());
1836        
1837        Map<String, Double> hoursByNature = new HashMap<>();
1838        List<CoursePart> courseParts = course.getCourseParts();
1839        for (CoursePart coursePart : courseParts)
1840        {
1841            String nature = coursePart.getNature();
1842            Double hours = MapUtils.getDouble(hoursByNature, nature, 0d) + coursePart.getNumberOfHours();
1843            if (hours > 0)
1844            {
1845                hoursByNature.put(nature, hours);
1846            }
1847        }
1848        
1849        for (String nature : hoursByNature.keySet())
1850        {
1851            attrs = new AttributesImpl();
1852            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_TEACHING_TYPE, _refTableHelper.getItemCDMfrValue(nature, true));
1853            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_GLOBAL_VOLUME, attrs, String.valueOf(hoursByNature.get(nature)));
1854        }
1855        
1856        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_CREDITS);
1857    }
1858
1859    /**
1860     * SAX the syllabus elements of the course.
1861     * @param contentHandler The content handler
1862     * @param course The course to SAX
1863     * @param courseRichTextsContext the context for course's rich texts
1864     * @throws SAXException if an error occurs
1865     */
1866    protected void _syllabus2CDM(ContentHandler contentHandler, Course course, DataContext courseRichTextsContext) throws SAXException
1867    {
1868        RichText syllabus = course.getSyllabus();
1869        RichText bibliography = course.getBibliography();
1870        Set<LOMSheet> lomSheets = course.getLOMSheets();
1871        if (syllabus != null || bibliography != null || !lomSheets.isEmpty())
1872        {
1873            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_SYLLABUS);
1874            
1875            // Syllabus
1876            if (syllabus != null)
1877            {
1878                CDMHelper.richText2CDM(contentHandler, syllabus, courseRichTextsContext.cloneContext().withDataPath(Course.SYLLABUS), _sourceResolver, isExportForAmetys());
1879            }
1880            
1881            // Bibliography
1882            if (bibliography != null)
1883            {
1884                CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + CDMFRTagsConstants.TAG_BIBLIOGRAPHY, bibliography, courseRichTextsContext.cloneContext().withDataPath(Course.BIBLIOGRAPHY), _sourceResolver, false, isExportForAmetys());
1885            }
1886            
1887            // LOM sheets
1888            for (LOMSheet lomSheet : lomSheets)
1889            {
1890                lomSheet.toCDM(contentHandler);
1891            }
1892            
1893            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_SYLLABUS);
1894        }
1895    }
1896    
1897    /**
1898     * Sax the course teaching locations.
1899     * @param contentHandler the content handler to SAX into
1900     * @param course The course to sax
1901     * @throws SAXException if failed to generate CDM
1902     */
1903    protected void _teachingLocation2CDM(ContentHandler contentHandler, Course course) throws SAXException
1904    {
1905        for (String id : course.getTeachingLocation())
1906        {
1907            if (StringUtils.isNotEmpty(id))
1908            {
1909                String postalCode = _refTableHelper.getItemCode(id);
1910                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_PLACE);
1911                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REF_ORG_UNIT);
1912                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
1913                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PCODE, postalCode);
1914                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
1915                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_TEACHING_PLACE);
1916            }
1917        }
1918    }
1919    
1920    /**
1921     * SAX CDMfr events for a {@link Course} contacts
1922     * @param contentHandler the content handler to sax into
1923     * @param course the course
1924     * @param persons the contacts
1925     * @param orgUnits the orgunits
1926     * @throws SAXException if an error occurred
1927     */
1928    protected void _course2CDMContacts(ContentHandler contentHandler, Course course, Set<String> persons, Set<String> orgUnits) throws SAXException
1929    {
1930        AttributesImpl attrs = new AttributesImpl();
1931        
1932        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
1933        
1934        Map<String, List<String>> contactsByRole = course.getContactsByRole();
1935        
1936        for (Entry<String, List<String>> entry : contactsByRole.entrySet())
1937        {
1938            String role = entry.getKey();
1939            List<String> contactIds = entry.getValue();
1940            
1941            String roleCode = null;
1942            if (StringUtils.isNotEmpty(role))
1943            {
1944                roleCode = _refTableHelper.getItemCode(role);
1945            }
1946            
1947            for (String personID : contactIds)
1948            {
1949                try
1950                {
1951                    Person person = _resolver.resolveById(personID);
1952                    persons.add(person.getId());
1953                    attrs = new AttributesImpl();
1954                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, person.getCDMId());
1955                    if (roleCode != null)
1956                    {
1957                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ROLE, roleCode);
1958                    }
1959                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_PERSON, attrs);
1960                }
1961                catch (UnknownAmetysObjectException e)
1962                {
1963                    // Contact does exist anymore, ignore it
1964                }
1965            }
1966        }
1967        
1968        for (String orgUnitID : course.getOrgUnits())
1969        {
1970            if (!"".equals(orgUnitID))
1971            {
1972                try
1973                {
1974                    OrgUnit orgUnit = _resolver.resolveById(orgUnitID);
1975                    orgUnits.add(orgUnit.getId());
1976                    attrs = new AttributesImpl();
1977                    attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, orgUnit.getCDMId());
1978                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_ORG_UNIT, attrs);
1979                }
1980                catch (UnknownAmetysObjectException e)
1981                {
1982                    // OrgUnit does exist anymore, ignore it
1983                }
1984            }
1985        }
1986        
1987        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
1988    }
1989    
1990    
1991    //***********************************************************************//
1992    //                              ORGUNIT                                  //
1993    //***********************************************************************//
1994    
1995    /**
1996     * Export this entity in CDM-fr format.
1997     * @param contentHandler the target handler
1998     * @param orgunit the orgunit
1999     * @throws SAXException if an error occurs during export.
2000     */
2001    public void orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit) throws SAXException
2002    {
2003        Set<String> persons = new HashSet<>();
2004        
2005        _orgunit2CDM(contentHandler, orgunit, persons, true);
2006        
2007        for (String personId : persons)
2008        {
2009            Person person = _resolver.resolveById(personId);
2010            person2CDM(contentHandler, person);
2011        }
2012        
2013        if (!orgunit.getId().equals(_rootOrgUnitProvider.getRootId()))
2014        {
2015            OrgUnit rootOrgunit = _resolver.resolveById(_rootOrgUnitProvider.getRootId());
2016            _orgunit2CDM(contentHandler, rootOrgunit, persons);
2017        }
2018        
2019        // Set content in request
2020        ContextHelper.getRequest(_context).setAttribute(Content.class.getName(), orgunit);
2021    }
2022    
2023    /**
2024     * SAX CDMfr event for a {@link OrgUnit}
2025     * @param contentHandler the content handler to sax into
2026     * @param orgunit the orgunit
2027     * @param persons the contacts of orgunit
2028     * @throws SAXException if an error occurred
2029     */
2030    protected void _orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit, Set<String> persons) throws SAXException
2031    {
2032        _orgunit2CDM(contentHandler, orgunit, persons, false);
2033    }
2034    
2035    /**
2036     * SAX CDMfr event for a {@link OrgUnit}
2037     * @param contentHandler the content handler to sax into
2038     * @param orgunit the orgunit
2039     * @param persons the contacts of orgunit
2040     * @param showChildren true to sax child orgunits
2041     * @throws SAXException if an error occurred
2042     */
2043    protected void _orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit, Set<String> persons, boolean showChildren) throws SAXException
2044    {
2045        Request request = ContextHelper.getRequest(_context);
2046        Content currentContent = (Content) request.getAttribute(Content.class.getName());
2047        DataContext orgunitRichTextsContext = RepositoryDataContext.newInstance()
2048                                                                   .withObject(orgunit);
2049        
2050        try
2051        {
2052            _odfHelper.switchToLiveVersionIfNeeded(orgunit);
2053            
2054            request.setAttribute(Content.class.getName(), orgunit);
2055        
2056            AttributesImpl attrs = new AttributesImpl();
2057            
2058            // <orgunit id="FR_RNE_{rootOrgUnitId}_OR_{login}">
2059            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, orgunit.getCDMId());
2060            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT, attrs);
2061            
2062            // <orgUnitID>RNE</orgUnitID>
2063            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_ID, orgunit.getCDMId());
2064            
2065            // <orgUnitName><text>name</text></orgUnitID>
2066            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_NAME);
2067            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEXT, orgunit.getTitle());
2068            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_NAME);
2069            
2070            // <orgUnitAcronym>acronyme</orgUnitAcronym>
2071            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_ACRONYM, orgunit.getAcronym());
2072            
2073            // <orgUnitCode codeSet="codeUAI">code</orgUnitCode>
2074            attrs = new AttributesImpl();
2075            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_CODE_SET, "codeUAI");
2076            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_CODE, attrs, orgunit.getUAICode());
2077    
2078            // <orgUnitKind orgUnitKindCodeValueSet=""/>
2079            attrs = new AttributesImpl();
2080            String orgunitType = _refTableHelper.getItemCDMfrValue(orgunit.getType(), false);
2081            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ORG_UNIT_KIND_CODE_VALUESET, orgunitType);
2082            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_KIND);
2083            
2084            // <webLink>
2085            //      <href>href</href>
2086            //      <linkName>linkName</linkName>
2087            // </webLink>
2088            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_WEB_LINK);
2089            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_HREF, orgunit.getWebLinkURL());
2090            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_LINK_NAME, orgunit.getWebLinkLabel());
2091            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_WEB_LINK);
2092            
2093            // <orgUnitDescription>...</orgUnitDescription>
2094            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT_DESCRIPTION, orgunit.getDescription(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.DESCRIPTION), _sourceResolver, false, isExportForAmetys());
2095            
2096            // <admissionInfo>
2097            //      <admissionDescription>...</admissionDescription>
2098            // </admissionInfo>
2099            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
2100            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_ADMISSION_DESCRIPTION, orgunit.getAdmissionInfo(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.ADMISSION_INFO), _sourceResolver);
2101            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADMISSION_INFO);
2102            
2103            // <regulations>...</regulations>
2104            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_REGULATIONS, orgunit.getRegulations(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.REGULATIONS), _sourceResolver, false, isExportForAmetys());
2105            
2106            // <expenses>...</expenses>
2107            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_EXPENSES, orgunit.getExpenses(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.EXPENSES), _sourceResolver, false, isExportForAmetys());
2108            
2109            // <studentFacilities>...</studentFacilities>
2110            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_STUDENT_FACILITIES, orgunit.getStudentFacilities(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.STUDENT_FACILITIES), _sourceResolver, false, isExportForAmetys());
2111            
2112            // <universalAjustment>...</universalAjustment>
2113            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_UNIVERSAL_ADJUSTMENT, orgunit.getUniversalAdjustment(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.UNIVERSAL_ADJUSTMENT), _sourceResolver, false, isExportForAmetys());
2114            
2115            // <contacts>
2116            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
2117            
2118            for (String contactID : orgunit.getContacts())
2119            {
2120                if (StringUtils.isNotEmpty(contactID))
2121                {
2122                    try
2123                    {
2124                        Person person = _resolver.resolveById(contactID);
2125                        persons.add(person.getId());
2126                        attrs = new AttributesImpl();
2127                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, person.getCDMId());
2128                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ROLE, "contact");
2129                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_PERSON, attrs);
2130                    }
2131                    catch (UnknownAmetysObjectException e)
2132                    {
2133                        // Contact does exist anymore, ignore it
2134                    }
2135                }
2136            }
2137            
2138            // </contacts>
2139            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_CONTACTS);
2140            
2141            // <infoBlock>...</infoBlock>
2142            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, orgunit.getAdditionnalInfos(), orgunitRichTextsContext.cloneContext().withDataPath(OrgUnit.ADDITIONNAL_INFOS), _sourceResolver, false, isExportForAmetys());
2143    
2144            Set<OrgUnit> orgUnits = new HashSet<>();
2145            if (showChildren)
2146            {
2147                // <refOrgUnit IDref=""/>
2148                for (String orgUnitID : orgunit.getSubOrgUnits())
2149                {
2150                    try
2151                    {
2152                        OrgUnit orgUnit = _resolver.resolveById(orgUnitID);
2153                        orgUnits.add(orgUnit);
2154                        attrs = new AttributesImpl();
2155                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, orgUnit.getCDMId());
2156                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_REF_ORG_UNIT, attrs);
2157                    }
2158                    catch (UnknownAmetysObjectException e)
2159                    {
2160                        // OrgUnit does exist anymore, ignore it
2161                    }
2162                }
2163            }
2164            
2165            // <infoBlock userDefined="ametys-extension>...</infoBlock>
2166            saxExtensions(contentHandler, orgunit, null, null);
2167                    
2168            // </orgunit>
2169            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ORG_UNIT);
2170    
2171            for (OrgUnit childOrgunit : orgUnits)
2172            {
2173                _orgunit2CDM(contentHandler, childOrgunit, persons);
2174            }
2175        }
2176        catch (NoLiveVersionException e)
2177        {
2178            getLogger().error("Orgunit {} ({}) is not validated so it cannot be exported to CDM-fr.", orgunit.getTitle(), orgunit.getId());
2179        }
2180        finally
2181        {
2182            request.setAttribute(Content.class.getName(), currentContent);
2183        }
2184    }
2185    
2186    
2187    //***********************************************************************//
2188    //                              PERSON                                   //
2189    //***********************************************************************//
2190    
2191    /**
2192     * Export this entity in CDM-fr format.
2193     * @param contentHandler the target handler
2194     * @param person the person
2195     * @throws SAXException if an error occurs during export.
2196     */
2197    public void person2CDM(ContentHandler contentHandler, Person person) throws SAXException
2198    {
2199        Request request = ContextHelper.getRequest(_context);
2200        Content currentContent = (Content) request.getAttribute(Content.class.getName());
2201        
2202        try
2203        {
2204            _odfHelper.switchToLiveVersionIfNeeded(person);
2205            
2206            request.setAttribute(Content.class.getName(), person);
2207            
2208            AttributesImpl attrs = new AttributesImpl();
2209            
2210            // <person id="FRUAI{rootOrgUnitId}PE{login}">
2211            attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID, person.getCDMId());
2212            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_PERSON, attrs);
2213            
2214            // <personID>login</personID>
2215            String login = person.getLogin();
2216            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PERSON_ID, StringUtils.isNotEmpty(login) ? login : person.getName());
2217            
2218            // <name>
2219            //      <given>prénom</given>
2220            //      <family>nom</family>
2221            // </name>
2222            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_NAME);
2223            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_GIVEN_NAME, person.getGivenName());
2224            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_FAMILY_NAME, person.getLastName());
2225            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_NAME);
2226    
2227            // <title><text>titre</text></title>
2228            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_TITLE);
2229            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEXT, person.getPersonTitle());
2230            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_TITLE);
2231            
2232            // <role><text>role</text></role>
2233            for (String role : person.getRole())
2234            {
2235                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ROLE);
2236                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_TEXT, role);
2237                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ROLE);
2238            }
2239            
2240            // <affiliation>
2241            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_AFFILIATION);
2242            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_REF_ORG_UNIT);
2243            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_PROFESS_FIELD);
2244            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_SECT_CNU);
2245            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_CDMFR + CDMFRTagsConstants.TAG_AFFILIATION);
2246            
2247            
2248            // <contactData>
2249            ContactData contactData = person.getContactData();
2250            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_CONTACT_DATA);
2251            
2252            // <adr>
2253            //      <extadr>extadr</extadr>
2254            //      <street>address</street>
2255            //      <locality>locality</locality>
2256            //      <pcode>pcode</pcode>
2257            // </adr>
2258            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
2259            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_EXTADR, contactData.getAdditionalAddress());
2260            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_STREET, contactData.getAddress());
2261            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LOCALITY, contactData.getTown());
2262            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PCODE, contactData.getZipCode());
2263            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_ADDRESS);
2264            
2265            // <telephone>telephone</telephone>
2266            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_PHONE, contactData.getPhone());
2267            
2268            // <fax>fax</fax>
2269            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_FAX, contactData.getFax());
2270            
2271            // <email>email</email>
2272            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_EMAIL, contactData.getMail());
2273            
2274            // <webLink>
2275            //      <href>href</href>
2276            //      <linkName>linkName</linkName>
2277            // </webLink>
2278            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.TAG_WEB_LINK);
2279            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_HREF, contactData.getWebLinkUrl());
2280            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.TAG_LINK_NAME, contactData.getWebLinkLabel());
2281            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_WEB_LINK);
2282            
2283            // </contactData>
2284            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_CONTACT_DATA);
2285            
2286            // <infoBlock>...</infoBlock>
2287            DataContext context = RepositoryDataContext.newInstance()
2288                                                       .withObject(person)
2289                                                       .withDataPath(Person.ADDITIONAL_INFORMATIONS);
2290            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.TAG_INFO_BLOCK, person.getAdditionalInformations(), context, _sourceResolver, false, isExportForAmetys());
2291            
2292            // <infoBlock userDefined="ametys-extension>...</infoBlock>
2293            saxExtensions(contentHandler, person, null, null);
2294    
2295            // </person>
2296            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.TAG_PERSON);
2297        }
2298        catch (NoLiveVersionException e)
2299        {
2300            getLogger().error("Person {} ({}) is not validated so it cannot be exported to CDM-fr.", person.getTitle(), person.getId());
2301        }
2302        finally
2303        {
2304            request.setAttribute(Content.class.getName(), currentContent);
2305        }
2306    }
2307    
2308    private String _getMentionType(Program program)
2309    {
2310        return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.DEGREE))
2311            .flatMap(ContentValue::getContentIfExists)
2312            .map(c -> c.<String>getValue(OdfReferenceTableHelper.DEGREE_MENTION_TYPE))
2313            .orElse(null);
2314    }
2315    
2316    //***********************************************************************//
2317    //                               MISC                                    //
2318    //***********************************************************************//
2319
2320    /**
2321     * SAX value as attributes if not null nor empty
2322     * @param attrs the XML attributes
2323     * @param localName the local name
2324     * @param value the value
2325     */
2326    protected void _addNotNullAttribute(AttributesImpl attrs, String localName, String value)
2327    {
2328        if (StringUtils.isNotEmpty(value))
2329        {
2330            attrs.addCDATAAttribute(localName, value);
2331        }
2332    }
2333    
2334    /**
2335     * SAX value as attributes if not null nor empty
2336     * @param attrs the XML attributes
2337     * @param localName the local name
2338     * @param value the value
2339     */
2340    protected void _addPositiveAttribute(AttributesImpl attrs, String localName, double value)
2341    {
2342        if (value > 0)
2343        {
2344            attrs.addCDATAAttribute(localName, String.valueOf(value));
2345        }
2346    }
2347}