001/*
002 *  Copyright 2011 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.enumeration;
017
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.xml.AttributesImpl;
031import org.apache.cocoon.xml.XMLUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.xml.sax.ContentHandler;
034import org.xml.sax.SAXException;
035
036import org.ametys.cms.contenttype.AbstractMetadataSetElement;
037import org.ametys.cms.contenttype.ContentConstants;
038import org.ametys.cms.contenttype.ContentType;
039import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
040import org.ametys.cms.contenttype.ContentTypesHelper;
041import org.ametys.cms.contenttype.MetadataDefinition;
042import org.ametys.cms.contenttype.MetadataDefinitionHolder;
043import org.ametys.cms.contenttype.MetadataDefinitionReference;
044import org.ametys.cms.contenttype.MetadataSet;
045import org.ametys.cms.contenttype.MetadataType;
046import org.ametys.cms.contenttype.RepeaterDefinition;
047import org.ametys.cms.repository.Content;
048import org.ametys.cms.repository.ContentQueryHelper;
049import org.ametys.cms.repository.ContentTypeExpression;
050import org.ametys.core.ui.Callable;
051import org.ametys.plugins.repository.AmetysObjectIterable;
052import org.ametys.plugins.repository.AmetysObjectIterator;
053import org.ametys.plugins.repository.AmetysObjectResolver;
054import org.ametys.plugins.repository.UnknownAmetysObjectException;
055import org.ametys.plugins.repository.query.expression.AndExpression;
056import org.ametys.plugins.repository.query.expression.Expression.Operator;
057import org.ametys.plugins.repository.query.expression.StringExpression;
058import org.ametys.runtime.config.Config;
059import org.ametys.runtime.plugin.component.AbstractLogEnabled;
060
061/**
062 * This component handles ODF reference tables 
063 *
064 */
065public class OdfReferenceTableHelper extends AbstractLogEnabled implements Component, Serviceable
066{
067    /** Avalon Role */
068    public static final String ROLE = OdfReferenceTableHelper.class.getName();
069 
070    /** Abstract table ref */
071    public static final String ABSTRACT_TABLE_REF = "odf-enumeration.AbstractTableRef";
072    /** Domain */
073    public static final String DOMAIN = "odf-enumeration.Domain";
074    /** Degree */
075    public static final String DEGREE = "odf-enumeration.Degree";
076    /** Level */
077    public static final String LEVEL = "odf-enumeration.Level";
078    /** Program type. */
079    public static final String PROGRAM_TYPE = "odf-enumeration.ProgramType";
080    /** Form of teaching */
081    public static final String FORMOFTEACHING_METHOD = "odf-enumeration.FormofteachingMethod";
082    /** Orgnization of teaching */
083    public static final String FORMOFTEACHING_ORG = "odf-enumeration.FormofteachingOrg";
084    /** Teaching method */
085    public static final String TEACHING_ACTIVITY = "odf-enumeration.TeachingActivity";
086    /** Type of training course */
087    public static final String INTERNSHIP = "odf-enumeration.Internship";
088    /** Distance learning modalities */
089    public static final String DISTANCE_LEARNING_MODALITIES = "odf-enumeration.DistanceLearningModalities";
090    /** Place */
091    public static final String PLACE = "odf-enumeration.Place";
092    /** Teaching term. */
093    public static final String TEACHING_TERM = "odf-enumeration.TeachingTerm";
094    /** Time slot */
095    public static final String TIME_SLOT = "odf-enumeration.TimeSlot";
096    /** Code ROME */
097    public static final String CODE_ROME = "odf-enumeration.CodeRome";
098    /** Code ERASMUS */
099    public static final String CODE_ERASMUS = "odf-enumeration.CodeErasmus";
100    /** Code DGESIP */
101    public static final String CODE_DGESIP = "odf-enumeration.CodeDgesip";
102    /** Code SISE */
103    public static final String CODE_SISE = "odf-enumeration.CodeSise";
104    /** Code Cite97 */
105    public static final String CODE_CITE97 = "odf-enumeration.CodeCite97";
106    /** Code FAP */
107    public static final String CODE_FAP = "odf-enumeration.CodeFap";
108    /** Code NSF */
109    public static final String CODE_NSF = "odf-enumeration.CodeNsf";
110    /** RNCP level */
111    public static final String RNCP_LEVEL = "odf-enumeration.RncpLevel";
112    /** Join orgunit*/
113    public static final String JOIN_ORGUNIT = "odf-enumeration.JoinOrgunit";
114    /** Mention licence */
115    public static final String ABSTRACT_MENTION = "odf-enumeration.Mention";
116    /** Mention licence */
117    public static final String MENTION_LICENCE = "odf-enumeration.MentionLicence";
118    /** Mention licence pro */
119    public static final String MENTION_LICENCEPRO = "odf-enumeration.MentionLicencepro";
120    /** Mention master */
121    public static final String MENTION_MASTER = "odf-enumeration.MentionMaster";
122    /** Abstract table ref for category */
123    public static final String ABSTRACT_TABLE_REF_CATEGORY = "odf-enumeration.AbstractTableRefCategory";
124    /** Apprenticeship contract */
125    public static final String APPRENTICESHIP_CONTRACT = "odf-enumeration.ApprenticeshipContract";
126    /** Available certification */
127    public static final String AVAILABLE_CERTIFICATION = "odf-enumeration.AvailableCertification";
128    /** Campus */
129    public static final String CAMPUS = "odf-enumeration.Campus";
130    /** Category for code Erasmus */
131    public static final String CODE_ERASMUS_CATEGORY = "odf-enumeration.CodeErasmusCategory";
132    /** Category for code FAP */
133    public static final String CODE_FAP_CATEGORY = "odf-enumeration.CodeFapCategory";
134    /** Nature of container */
135    public static final String CONTAINER_NATURE = "odf-enumeration.ContainerNature";
136    /** Nature of course */
137    public static final String COURSE_NATURE = "odf-enumeration.CourseNature";
138    /** Discipline */
139    public static final String DISCIPLINE = "odf-enumeration.Discipline";
140    /** Duration */
141    public static final String DURATION = "odf-enumeration.Duration";
142    /** ECTS */
143    public static final String ECTS = "odf-enumeration.Ects";
144    /** Foreign place */
145    public static final String FOREIGN_PLACE = "odf-enumeration.ForeignPlace";
146    /** International education */
147    public static final String INTERNATIONAL_EDUCATION = "odf-enumeration.InternationalEducation";
148    /** Language */
149    public static final String LANGUAGE = "odf-enumeration.Language";
150    /** OrgUnit type */
151    public static final String ORGUNIT_TYPE = "odf-enumeration.OrgUnitType";
152    /** Period */
153    public static final String PERIOD = "odf-enumeration.Period";
154    /** Person role */
155    public static final String PERSON_ROLE = "odf-enumeration.PersonRole";
156    /** Program field */
157    public static final String PROGRAM_FIELD = "odf-enumeration.ProgramField";
158    /** Sectors */
159    public static final String SECTORS = "odf-enumeration.Sectors";
160    /** Nature of course part */
161    public static final String ENSEIGNEMENT_NATURE = "odf-enumeration.EnseignementNature";
162    /** Category of nature of course part */
163    public static final String ENSEIGNEMENT_NATURE_CATEGORY = "odf-enumeration.EnseignementNatureCategory";
164    /** Skill */
165    public static final String SKILL = "odf-enumeration.Skill";
166    /** Skill set */
167    public static final String SKILL_SET = "odf-enumeration.SkillSet";
168    
169    private AmetysObjectResolver _resolver;
170
171    private ContentTypeExtensionPoint _cTypeEP;
172
173    private ContentTypesHelper _cTypeHelper;
174
175    @Override
176    public void service(ServiceManager manager) throws ServiceException
177    {
178        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
179        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
180        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
181    } 
182    
183    /**
184     * Determines if the content type is a ODF table reference
185     * @param cTypeId The id of content type
186     * @return true if the content type is a ODF table reference
187     */
188    public boolean isTableReference(String cTypeId)
189    {
190        return _cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF);
191    }
192    
193    /**
194     * Determines if the content is an entry of a ODF table reference
195     * @param content The content 
196     * @return <code>true</code> if the content is an entry of a ODF table reference
197     */
198    public boolean isTableReferenceEntry(Content content)
199    {
200        String[] cTypeIds = content.getTypes();
201        for (String cTypeId : cTypeIds)
202        {
203            if (isTableReference(cTypeId))
204            {
205                return true;
206            }
207        }
208        return false;
209    }
210    
211    /**
212     * Get the id of table references
213     * @return The content type's id
214     */
215    public Set<String> getTableReferenceIds()
216    {
217        Set<String> tableRefIds = new HashSet<>();
218        
219        for (String cTypeId : _cTypeEP.getExtensionsIds())
220        {
221            if (_cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF))
222            {
223                tableRefIds.add(cTypeId);
224            }
225        }
226        return tableRefIds;
227    }
228    
229    /**
230     * Get the metadata definitions for table references for a given content type
231     * @param cTypeId The id of content type
232     * @return The metadata definitions for table references
233     */
234    public Map<String, MetadataDefinition> getTableRefMetadataDefinition(String cTypeId)
235    {
236        ContentType cType = _cTypeEP.getExtension(cTypeId);
237        return _getTableRefMetadata(cType, "");
238    }
239    
240    /**
241     * Get the metadata definitions for table references for a given content type and a metadata set
242     * @param cTypeId The id of content type
243     * @param medatataSetName the name of the metadata set. Cannot be null.
244     * @param isEditionMetadataSet true if the metadata set is for edition.
245     * @return The metadata definitions for table references
246     */
247    public Map<String, MetadataDefinition> getTableRefMetadataDefinition(String cTypeId, String medatataSetName, boolean isEditionMetadataSet)
248    {
249        ContentType cType = _cTypeEP.getExtension(cTypeId);
250        
251        MetadataSet metadataSet = isEditionMetadataSet ? cType.getMetadataSetForEdition(medatataSetName) : cType.getMetadataSetForView(medatataSetName);
252        return _getTableRefMetadata(metadataSet, cType, "");
253    }
254    
255    private Map<String, MetadataDefinition> _getTableRefMetadata(MetadataDefinitionHolder parentMetaDef, String prefix)
256    {
257        Map<String, MetadataDefinition> tableRefMetadata = new LinkedHashMap<>();
258        
259        Set<String> metadataNames = parentMetaDef.getMetadataNames();
260        for (String metadataName : metadataNames)
261        {
262            MetadataDefinition metaDef = parentMetaDef.getMetadataDefinition(metadataName);
263            MetadataType type = metaDef.getType();
264            
265            switch (type)
266            {
267                case COMPOSITE:
268                    if (!(metaDef instanceof RepeaterDefinition))
269                    {
270                        // enumerated metadata in repeaters are not supported
271                        tableRefMetadata.putAll(_getTableRefMetadata(metaDef, prefix + metadataName + ContentConstants.METADATA_PATH_SEPARATOR)); 
272                    }
273                    break;
274
275                case CONTENT:
276                    if (isTableReference(metaDef.getContentType()))
277                    {
278                        tableRefMetadata.put(prefix + metadataName, metaDef);
279                    }
280                    break;
281                    
282                default:
283                    break;
284            }
285        }
286        
287        return tableRefMetadata;
288    }
289    
290    private Map<String, MetadataDefinition> _getTableRefMetadata (AbstractMetadataSetElement element, MetadataDefinitionHolder parentMetaDef, String prefix)
291    {
292        Map<String, MetadataDefinition> tableRefMetadata = new LinkedHashMap<>();
293        
294        for (AbstractMetadataSetElement subElement : element.getElements())
295        {
296            if (subElement instanceof MetadataDefinitionReference)
297            {
298                MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement;
299                String metadataName = metadataDefRef.getMetadataName();
300                MetadataDefinition metaDef = parentMetaDef.getMetadataDefinition(metadataName);
301           
302                MetadataType type = metaDef.getType();
303                
304                switch (type)
305                {
306                    case COMPOSITE:
307                        if (!(metaDef instanceof RepeaterDefinition))
308                        {
309                            // table ref metadata in repeaters are not supported
310                            tableRefMetadata.putAll(_getTableRefMetadata(subElement, metaDef, prefix + metadataName + ContentConstants.METADATA_PATH_SEPARATOR)); 
311                        }
312                        break;
313
314                    case CONTENT:
315                        if (isTableReference(metaDef.getContentType()))
316                        {
317                            tableRefMetadata.put(prefix + metadataName, metaDef);
318                        }
319                        break;
320                        
321                    default:
322                        break;
323                }
324            }
325        }
326        return tableRefMetadata;
327    }
328    
329    /**
330     * Get the content type for mention for this degree
331     * @param degreeIds The ids of degrees
332     * @return A map with the id of content type or null if there is no mention for this degree
333     */
334    @Callable
335    public Map<String, String> getMentionContentTypes(List<String> degreeIds)
336    {
337        Map<String, String> mentionTypes = new HashMap<>();
338        for (String degreeId : degreeIds)
339        {
340            mentionTypes.put(degreeId, getMentionForDegree(degreeId));
341        }
342        return mentionTypes;
343    }
344    
345    /**
346     * Get the mention for a given degree.
347     * @param degreeId The degree ID
348     * @return The associated mention reference table, null if there isn't
349     */
350    public String getMentionForDegree(String degreeId)
351    {
352        try
353        {
354            Content content = _resolver.resolveById(degreeId);
355            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
356            
357            String degreeCode = entry.getCode();
358            
359            if (degreeCode.equals(Config.getInstance().getValue("odf.programs.degree.license")))
360            {
361                return OdfReferenceTableHelper.MENTION_LICENCE;
362            }
363            else if (degreeCode.equals(Config.getInstance().getValue("odf.programs.degree.licensepro")))
364            {
365                return OdfReferenceTableHelper.MENTION_LICENCEPRO;
366            }
367            else if (degreeCode.equals(Config.getInstance().getValue("odf.programs.degree.master")))
368            {
369                return OdfReferenceTableHelper.MENTION_MASTER;
370            }
371        }
372        catch (UnknownAmetysObjectException e)
373        {
374            // Nothing to do
375        }
376        return null;
377    }
378    
379    /**
380     * Get the CDM-fr value associated with the given code
381     * @param tableRefId The id of content type
382     * @param code The code
383     * @return The CDM-fr value or empty string if not found
384     */
385    public String getCDMfrValue (String tableRefId, String code)
386    {
387        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
388        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CODE, Operator.EQ, code);
389        
390        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
391        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
392        AmetysObjectIterator<Content> it = contents.iterator();
393        
394        if (it.hasNext())
395        {
396            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
397            return entry.getCdmValue();
398        }
399        
400        return "";
401    }
402    
403    /**
404     * Get all items of an enumeration and their label
405     * @param tableRefId The id of content type
406     * @return items of enumeration
407     */
408    public List<OdfReferenceTableEntry> getItems (String tableRefId)
409    {
410        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
411        
412        String xpathQuery = ContentQueryHelper.getContentXPathQuery(cTypeExpr);
413        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
414        
415        return contents.stream().map(c -> new OdfReferenceTableEntry(c)).collect(Collectors.toList());
416    }
417    
418    /**
419     * Returns the label of an reference table entry
420     * @param contentId The content id
421     * @param lang The requested language of label
422     * @return the item label or <code>null</code> if not found
423     */
424    public String getItemLabel(String contentId, String lang)
425    {
426        try
427        {
428            Content content = _resolver.resolveById(contentId);
429            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
430            return entry.getLabel(lang);
431        }
432        catch (UnknownAmetysObjectException e)
433        {
434            return null;
435        }
436    }
437    
438    /**
439     * Returns the label of an reference table entry
440     * @param tableRefId The id of content type (useless)
441     * @param contentId The content id
442     * @param lang The requested language of label
443     * @return the item label or <code>null</code> if not found
444     * @deprecated Use {@link #getItemLabel(String, String)} instead
445     */
446    @Deprecated
447    public String getItemLabel (String tableRefId, String contentId, String lang)
448    {
449        return getItemLabel(contentId, lang);
450    }
451
452    /**
453     * Returns the CMD value of an reference table entry
454     * @param contentId The content id
455     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
456     * @return the CDM-fr value or empty value if not found
457     */
458    public String getItemCDMfrValue(String contentId, boolean returnCodeIfEmpty)
459    {
460        if (StringUtils.isEmpty(contentId))
461        {
462            return "";
463        }
464        
465        Content content = _resolver.resolveById(contentId);
466        OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
467        
468        String cdmValue = entry.getCdmValue();
469        
470        if (StringUtils.isEmpty(cdmValue) && returnCodeIfEmpty)
471        {
472            return entry.getCode();
473        }
474        return cdmValue;
475    }
476    
477    /**
478     * Returns the CMD value of an reference table entry
479     * @param tableRefId The id of content type (useless)
480     * @param contentId The content id
481     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
482     * @return the CDM-fr value or empty value if not found
483     * @deprecated Use {@link #getItemCDMfrValue(String, boolean)} instead
484     */
485    @Deprecated
486    public String getItemCDMfrValue (String tableRefId, String contentId, boolean returnCodeIfEmpty)
487    {
488        return getItemCDMfrValue(contentId, returnCodeIfEmpty);
489    }
490    
491    /**
492     * Returns the code of an reference table entry from its CDM value
493     * @param contentId The id of content
494     * @return the code or empty value if not found
495     */
496    public String getItemCode(String contentId)
497    {
498        if (StringUtils.isEmpty(contentId))
499        {
500            return "";
501        }
502        
503        try
504        {
505            Content content = _resolver.resolveById(contentId);
506            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
507            return entry.getCode();
508        }
509        catch (UnknownAmetysObjectException e)
510        {
511            return "";
512        }
513    }
514    
515    /**
516     * Returns the code of an reference table entry from its CDM value
517     * @param tableRefId The id of content type (useless)
518     * @param contentId The id of content
519     * @return the code or empty value if not found
520     * @deprecated Use {@link #getItemCode(String)} instead
521     */
522    @Deprecated
523    public String getItemCode (String tableRefId, String contentId)
524    {
525        return getItemCode(contentId);
526    }
527    
528    /**
529     * Returns the code of an reference table entry from its CDM value
530     * @param tableRefId The id of content type
531     * @param cdmValue The CDM-fr value
532     * @return the code or <code>null</code> if not found
533     */
534    public String getItemCodeFromCDM (String tableRefId, String cdmValue)
535    {
536        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
537        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CDM_VALUE, Operator.EQ, cdmValue);
538        
539        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
540        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
541        AmetysObjectIterator<Content> it = contents.iterator();
542        
543        if (it.hasNext())
544        {
545            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
546            return entry.getCode();
547        }
548        return null;
549    }
550
551    /**
552     * Returns the entry of an reference table entry from its cdmValue
553     * @param tableRefId The id of content type
554     * @param cdmValue The CDM-fr value
555     * @return the entry or <code>null</code> if not found
556     */
557    public OdfReferenceTableEntry getItemFromCDM(String tableRefId, String cdmValue)
558    {
559        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
560        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CDM_VALUE, Operator.EQ, cdmValue);
561        
562        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
563
564        return _resolver.<Content>query(xpathQuery).stream()
565            .findFirst()
566            .map(OdfReferenceTableEntry::new)
567            .orElse(null);
568    }
569    
570    /**
571     * Returns the entry of an reference table entry from its code
572     * @param tableRefId The id of content type
573     * @param code The code
574     * @return the entry or <code>null</code> if not found
575     */
576    public OdfReferenceTableEntry getItemFromCode(String tableRefId, String code)
577    {
578        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
579        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CODE, Operator.EQ, code);
580        
581        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
582        
583        return _resolver.<Content>query(xpathQuery).stream()
584            .findFirst()
585            .map(OdfReferenceTableEntry::new)
586            .orElse(null);
587    }
588    
589    /**
590     * SAX items of a reference table
591     * @param contentHandler The content handler to sax into
592     * @param tableRefId The id of reference table
593     * @throws SAXException if an error occurred while saxing
594     */
595    public void saxItems (ContentHandler contentHandler, String tableRefId) throws SAXException
596    {
597        _saxItems(contentHandler, new AttributesImpl(), tableRefId);
598    }
599    
600    /**
601     * SAX items of a reference table
602     * @param contentHandler the content handler to sax into
603     * @param metaDef the metadata definition
604     * @throws SAXException if an error occurs while saxing
605     */
606    public void saxItems (ContentHandler contentHandler, MetadataDefinition metaDef) throws SAXException
607    {
608        if (metaDef.getType() == MetadataType.CONTENT)
609        {
610            String cTypeId = metaDef.getContentType();
611            if (cTypeId.startsWith("odf-enumeration."))
612            {
613                AttributesImpl attrs = new AttributesImpl();
614                attrs.addCDATAAttribute("metadataName", metaDef.getName());
615                attrs.addCDATAAttribute("metadataPath", metaDef.getId());
616                
617                _saxItems(contentHandler, attrs, cTypeId);
618            }
619        }
620    }
621    
622    private void _saxItems (ContentHandler contentHandler, AttributesImpl rootAttrs, String tableRefId) throws SAXException
623    {
624        rootAttrs.addCDATAAttribute("contentTypeId", tableRefId);
625        XMLUtils.startElement(contentHandler, "items", rootAttrs);
626        
627        List<OdfReferenceTableEntry> entries = getItems(tableRefId);
628        for (OdfReferenceTableEntry entry : entries)
629        {
630            AttributesImpl valueAttrs = new AttributesImpl();
631            valueAttrs.addCDATAAttribute("id", entry.getId());
632            valueAttrs.addCDATAAttribute("code", entry.getCode());
633            valueAttrs.addCDATAAttribute("cdmValue", entry.getCdmValue());
634            
635            String lang = Config.getInstance().getValue("odf.programs.lang");
636            XMLUtils.createElement(contentHandler, "item", valueAttrs, entry.getLabel(lang));
637        }
638        
639        XMLUtils.endElement(contentHandler, "items");
640    }
641}