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