001    /*
002     * 
003     * $Revision: 15374 $ $Date: 2009-06-15 17:23:48 +0200 (Mon, 15 Jun 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.datamodel.classifications2.utils;
025    
026    import static org.jdom.Namespace.XML_NAMESPACE;
027    import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
028    import static org.mycore.common.MCRConstants.XSI_NAMESPACE;
029    
030    import java.net.URI;
031    import java.util.Arrays;
032    import java.util.Comparator;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.regex.Matcher;
037    import java.util.regex.Pattern;
038    
039    import org.jdom.Document;
040    import org.jdom.Element;
041    import org.mycore.datamodel.classifications2.MCRCategLinkServiceFactory;
042    import org.mycore.datamodel.classifications2.MCRCategory;
043    import org.mycore.datamodel.classifications2.MCRCategoryID;
044    import org.mycore.datamodel.classifications2.MCRLabel;
045    
046    /**
047     * 
048     * @author Thomas Scheffler (yagee)
049     * 
050     * @version $Revision: 15374 $ $Date: 2008-02-06 17:27:24 +0000 (Mi, 06 Feb
051     *          2008) $
052     */
053    public class MCRCategoryTransformer {
054    
055        private static final String STANDARD_LABEL = "{text}";
056    
057        /**
058         * transforms a <code>MCRCategory</code> into a JDOM Document.
059         * 
060         * The Document will have a root tag with name "mycoreclass".
061         * 
062         * @param cl
063         *            Classification
064         * @return
065         */
066        public static Document getMetaDataDocument(MCRCategory cl, boolean withCounter) {
067            Map<MCRCategoryID, Number> countMap = null;
068            if (withCounter) {
069                countMap = MCRCategLinkServiceFactory.getInstance().countLinks(cl, false);
070            }
071            return MetaDataElementFactory.getDocument(cl, countMap);
072        }
073    
074        /**
075         * transforms a <code>MCRCategory</code> into a JDOM Element.
076         * 
077         * The element will have the tag name "category".
078         * 
079         * @param category
080         *            a category of a classification
081         * @return
082         */
083        public static Element getMetaDataElement(MCRCategory category, boolean withCounter) {
084            Map<MCRCategoryID, Number> countMap = null;
085            if (withCounter) {
086                countMap = MCRCategLinkServiceFactory.getInstance().countLinks(category, false);
087            }
088            return MetaDataElementFactory.getElement(category, countMap);
089        }
090    
091        /**
092         * transforms a <code>Classification</code> into a MCR Editor definition (
093         * <code>&lt;items&gt;</code>).
094         * 
095         * @param cl
096         *            Classification
097         * @param sort
098         *            if true, sort items
099         * @param emptyLeaves
100         *            if true, also include empty leaves
101         * @return
102         */
103        public static Element getEditorItems(MCRCategory cl, boolean sort, boolean emptyLeaves) {
104            return new ItemElementFactory(cl, STANDARD_LABEL, sort, emptyLeaves).getResult();
105        }
106    
107        /**
108         * transforms a <code>Classification</code> into a MCR Editor definition (<code>&lt;items&gt;</code>).
109         * 
110         * This method allows you to specify how the labels will look like.
111         * <code>labelFormat</code> is simply a String that is parsed for a few
112         * key words, that will be replaced by a dynamic value. The following
113         * keywords can be used at the moment:
114         * <ul>
115         * <li>{id}</li>
116         * <li>{text}</li>
117         * <li>{description}</li>
118         * <li>{count}</li>
119         * </ul>
120         * 
121         * @param cl
122         *            Classification
123         * @param labelFormat
124         *            format String as specified above
125         * @param sort
126         *            if true, sort items
127         * @param emptyLeaves
128         *            if true, also include empty leaves
129         * @return
130         */
131        public static Element getEditorItems(MCRCategory cl, String labelFormat, boolean sort, boolean emptyLeaves) {
132            return new ItemElementFactory(cl, labelFormat, sort, emptyLeaves).getResult();
133        }
134    
135        private static class MetaDataElementFactory {
136            static Document getDocument(MCRCategory cl, Map<MCRCategoryID, Number> countMap) {
137                Document cd = new Document(new Element("mycoreclass"));
138                cd.getRootElement().setAttribute("noNamespaceSchemaLocation", "MCRClassification.xsd", XSI_NAMESPACE);
139                cd.getRootElement().setAttribute("ID", cl.getId().getRootID());
140                cd.getRootElement().addNamespaceDeclaration(XLINK_NAMESPACE);
141                MCRCategory root = cl.isClassification() ? cl : cl.getRoot();
142                for (MCRLabel label : root.getLabels()) {
143                    cd.getRootElement().addContent(getElement(label));
144                }
145                Element categories = new Element("categories");
146                cd.getRootElement().addContent(categories);
147                if (cl.isClassification()) {
148                    for (MCRCategory category : cl.getChildren()) {
149                        categories.addContent(getElement(category, countMap));
150                    }
151                } else {
152                    categories.addContent(getElement(cl, countMap));
153                }
154                return cd;
155            }
156    
157            static Element getElement(MCRLabel label) {
158                Element le = new Element("label");
159                if (stringNotEmpty(label.getLang())) {
160                    le.setAttribute("lang", label.getLang(), XML_NAMESPACE);
161                }
162                if (stringNotEmpty(label.getText())) {
163                    le.setAttribute("text", label.getText());
164                }
165                if (stringNotEmpty(label.getDescription())) {
166                    le.setAttribute("description", label.getDescription());
167                }
168                return le;
169            }
170    
171            static Element getElement(MCRCategory category, Map<MCRCategoryID, Number> countMap) {
172                Element ce = new Element("category");
173                ce.setAttribute("ID", category.getId().getID());
174                Number number = (countMap == null) ? null : countMap.get(category.getId());
175                if (number != null) {
176                    ce.setAttribute("counter", Integer.toString(number.intValue()));
177                }
178                for (MCRLabel label : category.getLabels()) {
179                    ce.addContent(getElement(label));
180                }
181                if (category.getURI() != null) {
182                    URI link = category.getURI();
183                    ce.addContent(getElement(link));
184                }
185                for (MCRCategory cat : category.getChildren()) {
186                    ce.addContent(getElement(cat, countMap));
187                }
188                return ce;
189            }
190    
191            static Element getElement(URI link) {
192                Element le = new Element("url");
193                le.setAttribute("href", link.toString(), XLINK_NAMESPACE);
194                // TODO: Have to check url here: any samples?
195                le.setAttribute("type", "locator", XLINK_NAMESPACE);
196                return le;
197            }
198    
199            static boolean stringNotEmpty(String test) {
200                if (test != null && test.length() > 0) {
201                    return true;
202                }
203                return false;
204            }
205        }
206    
207        private static class ItemElementFactory {
208            private static final Pattern TEXT_PATTERN = Pattern.compile("\\{text\\}");
209    
210            private static final Pattern ID_PATTERN = Pattern.compile("\\{id\\}");
211    
212            private static final Pattern DESCR_PATTERN = Pattern.compile("\\{description\\}");
213    
214            private static final Pattern COUNT_PATTERN = Pattern.compile("\\{count(:([^\\)]+))?\\}");
215    
216            private String labelFormat;
217    
218            private boolean emptyLeaves;
219    
220            private Map<MCRCategoryID, Number> countMap = null;
221    
222            private Map<MCRCategoryID, Boolean> linkedMap = null;
223    
224            private Element root;
225    
226            ItemElementFactory(MCRCategory cl, String labelFormat, boolean sort, boolean emptyLeaves) {
227                this.labelFormat = labelFormat;
228                this.emptyLeaves = emptyLeaves;
229    
230                Matcher countMatcher = COUNT_PATTERN.matcher(labelFormat);
231                /*
232                 * countMatcher.group(0) is the whole expression string like
233                 * {count:document} countMatcher.group(1) is first inner expression
234                 * string like :document countMatcher.group(2) is most inner
235                 * expression string like document
236                 */
237                if (countMatcher.find()) {
238                    if (countMatcher.group(1) == null)
239                        countMap = MCRCategLinkServiceFactory.getInstance().countLinks(cl, false);
240                    else {
241                        // group(2) contains objectType
242                        String objectType = countMatcher.group(2);
243                        countMap = MCRCategLinkServiceFactory.getInstance().countLinksForType(cl, objectType, false);
244                    }
245                }
246                if (!emptyLeaves) {
247                    linkedMap = MCRCategLinkServiceFactory.getInstance().hasLinks(cl);
248                }
249    
250                root = new Element("items");
251                for (MCRCategory category : cl.getChildren()) {
252                    addChildren(root, category);
253                }
254                if (sort) {
255                    @SuppressWarnings("unchecked")
256                    final List<Element> items = root.getChildren("item");
257                    sortItems(items);
258                }
259            }
260    
261            Element getResult() {
262                return root;
263            }
264    
265            void addChildren(Element parent, MCRCategory category) {
266                if ((!emptyLeaves) && (!linkedMap.get(category.getId()).booleanValue()))
267                    return;
268    
269                Element ce = new Element("item");
270                ce.setAttribute("value", category.getId().getID());
271                parent.addContent(ce);
272    
273                for (MCRLabel label : category.getLabels()) {
274                    addLabel(ce, label, category);
275                }
276                for (MCRCategory cat : category.getChildren()) {
277                    addChildren(ce, cat);
278                }
279            }
280    
281            void addLabel(Element item, MCRLabel label, MCRCategory cat) {
282                Element le = new Element("label");
283                item.addContent(le);
284                if ((label.getLang() != null) && (label.getLang().length() > 0)) {
285                    le.setAttribute("lang", label.getLang(), XML_NAMESPACE);
286                }
287    
288                String labtext = (label.getText() != null ? label.getText() : "");
289                String labdesc = (label.getDescription() != null ? label.getDescription() : "");
290    
291                String text = TEXT_PATTERN.matcher(labelFormat).replaceAll(labtext);
292                text = ID_PATTERN.matcher(text).replaceAll(cat.getId().getID());
293                text = DESCR_PATTERN.matcher(text).replaceAll(labdesc);
294                int num = (countMap == null ? -1 : countMap.get(cat.getId()).intValue());
295                if (num >= 0) {
296                    text = COUNT_PATTERN.matcher(text).replaceAll(String.valueOf(num));
297                }
298    
299                le.setText(text);
300            }
301    
302            @SuppressWarnings("unchecked")
303            private void sortItems(List<Element> items) {
304                sort(items, MCREditorItemComparator.CURRENT_LANG_TEXT_ORDER);
305                Iterator<Element> it = items.iterator();
306                while (it.hasNext()) {
307                    Element item = it.next();
308                    List<Element> children = item.getChildren("item");
309                    if (children.size() > 0) {
310                        sortItems(children);
311                    }
312                }
313            }
314    
315            private void sort(List<Element> list, Comparator<Element> c) {
316                Element[] a = list.toArray(new Element[list.size()]);
317                Arrays.sort(a, c);
318                for (int i = 0; i < a.length; i++) {
319                    a[i].detach();
320                }
321                for (int i = 0; i < a.length; i++) {
322                    list.add(a[i]);
323                }
324            }
325        }
326    }