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.skinfactory.generators;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.regex.Pattern;
029
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.ProcessingException;
033import org.apache.cocoon.generation.ServiceableGenerator;
034import org.apache.cocoon.xml.AttributesImpl;
035import org.apache.cocoon.xml.XMLUtils;
036import org.apache.commons.lang.StringUtils;
037import org.apache.excalibur.xml.sax.SAXParser;
038import org.xml.sax.InputSource;
039import org.xml.sax.SAXException;
040
041import org.ametys.core.util.I18nUtils;
042import org.ametys.core.util.IgnoreRootHandler;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.skinfactory.SkinFactoryComponent;
045import org.ametys.skinfactory.parameters.AbstractSkinParameter;
046import org.ametys.skinfactory.parameters.AbstractSkinParameter.SkinParameterType;
047import org.ametys.web.skin.SkinModel;
048import org.ametys.web.skin.SkinModelsManager;
049
050/**
051 * Generate the ribbon file
052 */
053public class GenerateRibbonFile extends ServiceableGenerator
054{
055    private SkinFactoryComponent _skinFactoryManager;
056    private SkinModelsManager _modelsManager;
057    private I18nUtils _i18nUtils;
058    
059    private final HashMap<Pattern, String> _glyphAssociations = new LinkedHashMap<>();
060    {
061        _glyphAssociations.put(Pattern.compile("^(h1|h2|h3|h4)$"), "ametysicon-header");
062        _glyphAssociations.put(Pattern.compile("text"), "ametysicon-text");
063        _glyphAssociations.put(Pattern.compile("link"), "ametysicon-link23");
064        _glyphAssociations.put(Pattern.compile("list"), "ametysicon-list4");
065        _glyphAssociations.put(Pattern.compile("vmenu^$"), "ametysicon-vmenu");
066        _glyphAssociations.put(Pattern.compile("menu"), "ametysicon-menu");
067        _glyphAssociations.put(Pattern.compile("^(item|subitem)$"), "ametysicon-menu-element");
068        _glyphAssociations.put(Pattern.compile("title|header"), "ametysicon-header");
069        _glyphAssociations.put(Pattern.compile("table"), "ametysicon-tables1");
070        _glyphAssociations.put(Pattern.compile("border"), "ametysicon-tables1"); // FIXME when new icon has arrived
071        _glyphAssociations.put(Pattern.compile("image"), "ametysicon-image2");
072        // Default glyph for unknown style
073        _glyphAssociations.put(Pattern.compile("."), "ametysicon-designer");
074    }
075    
076    @Override
077    public void service(ServiceManager smanager) throws ServiceException 
078    {
079        super.service(smanager);
080        _skinFactoryManager = (SkinFactoryComponent) smanager.lookup(SkinFactoryComponent.ROLE);
081        _modelsManager = (SkinModelsManager)  smanager.lookup(SkinModelsManager.ROLE);
082        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
083    }
084    
085    @Override
086    public void generate() throws IOException, SAXException, ProcessingException
087    {
088        contentHandler.startDocument();
089        XMLUtils.startElement(contentHandler, "CMS");
090        
091        String modelName = parameters.getParameter("modelName", null);
092        SkinModel model = _modelsManager.getModel(modelName);
093        
094        Path ribbonFile = model.getPath().resolve("model/cms-ribbon.xml");
095        if (Files.exists(ribbonFile))
096        {
097            SAXParser saxParser = null;
098            try (InputStream is = Files.newInputStream(ribbonFile))
099            {
100                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
101                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
102            }
103            catch (ServiceException e)
104            {
105                throw new ProcessingException("Unable to get a SAX parser", e);
106            }
107            finally
108            {
109                manager.release(saxParser);
110            }
111        }
112        
113        Map<String, RibbonTab> tabs = _getGroupedParams(_skinFactoryManager.getModelParameters(modelName));
114        
115        XMLUtils.startElement(contentHandler, "parameters");
116        for (RibbonTab tab : tabs.values())
117        {
118            AttributesImpl attrs = new AttributesImpl();
119            attrs.addCDATAAttribute("id", tab.getId());
120            attrs.addCDATAAttribute("label", _i18nUtils.translate(tab.getLabel()));
121            XMLUtils.startElement(contentHandler, "tab", attrs);
122            
123            XMLUtils.startElement(contentHandler, "groups");
124            
125            Collection<RibbonGroup> groups = tab.getGroups();
126            for (RibbonGroup group : groups)
127            {
128                attrs = new AttributesImpl();
129                attrs.addCDATAAttribute("id", group.getId());
130                attrs.addCDATAAttribute("label", _i18nUtils.translate(group.getLabel()));
131                XMLUtils.startElement(contentHandler, "group", attrs);
132                
133                Collection<RibbonElement> elmts = group.getRibbonElements();
134                for (RibbonElement elt : elmts)
135                {
136                    if (elt instanceof ParameterControl)
137                    {
138                        attrs = new AttributesImpl();
139                        attrs.addCDATAAttribute("id", elt.getId());
140                        XMLUtils.createElement(contentHandler, "parameter", attrs);
141                    }
142                    else if (elt instanceof Menu)
143                    {
144                        _saxMenu ((Menu) elt);
145                    }
146                }
147                XMLUtils.endElement(contentHandler, "group");
148            }
149            XMLUtils.endElement(contentHandler, "groups");
150            
151            XMLUtils.endElement(contentHandler, "tab");
152        }
153        XMLUtils.endElement(contentHandler, "parameters");
154        
155        XMLUtils.endElement(contentHandler, "CMS");
156        contentHandler.endDocument();
157        
158    }
159    
160    private void _saxMenu (Menu menu) throws SAXException
161    {
162        AttributesImpl attrs = new AttributesImpl();
163        attrs.addCDATAAttribute("id", menu.getId());
164        attrs.addCDATAAttribute("label", _i18nUtils.translate(menu.getLabel()));
165        _saxMenuIcons (menu, attrs);
166        XMLUtils.startElement(contentHandler, "menu", attrs);
167        
168        for (String itemId : menu.getItems())
169        {
170            attrs = new AttributesImpl();
171            attrs.addCDATAAttribute("id", itemId);
172            XMLUtils.createElement(contentHandler, "parameter", attrs);
173        }
174        
175        for (Menu submenu : menu.getMenus())
176        {
177            _saxMenu(submenu);
178        }
179        
180        XMLUtils.endElement(contentHandler, "menu");
181    }
182    
183    private Map<String, RibbonTab> _getGroupedParams (Map<String, AbstractSkinParameter> skinParameters)
184    {
185        Map<String, RibbonTab> tabs = new HashMap<>();
186        
187        for (AbstractSkinParameter param : skinParameters.values())
188        {   
189            String id = param.getId();
190            String[] parts = id.split("\\.");
191            
192            String tabId = "org.ametys.skinfactory.Tab." + (parts.length >= 3 ? parts[0] : "default");
193            String groupId = parts.length >= 3 ? StringUtils.join(parts, ".", 0, 2) : "default.default";
194            String eltId = null;
195            
196            String itemId = null;
197            String subItemId = null;
198            
199            if (parts.length <= 3 || !param.getType().equals(SkinParameterType.CSS))
200            {
201                eltId = StringUtils.join(parts, ".");
202            }
203            else
204            {
205                eltId = StringUtils.join(parts, ".", 0, 3);
206                itemId = parts.length ==  4 ? StringUtils.join(parts, ".") : StringUtils.join(parts, ".", 0, 4);
207                
208                if (parts.length > 4)
209                {
210                    subItemId = StringUtils.join(parts, ".");
211                }
212            }
213            
214            _addGroupedParam(tabs, tabId, groupId, eltId, itemId, subItemId);
215        }
216        
217        return tabs;
218    }
219    
220    private void _addGroupedParam (Map<String, RibbonTab> tabs, String tabId, String groupId, String controlId, String itemId, String subItemId)
221    {
222        if (!tabs.containsKey(tabId))
223        {
224            tabs.put(tabId, new RibbonTab(tabId, new I18nizableText(tabId.substring(tabId.lastIndexOf(".") + 1))));
225        }
226        
227        RibbonTab ribbonTab = tabs.get(tabId);
228        if (!ribbonTab.hasGroup(groupId))
229        {
230            ribbonTab.addGroup(new RibbonGroup(groupId, new I18nizableText(groupId.substring(groupId.lastIndexOf(".") + 1))));
231        }
232        
233        RibbonGroup ribbonGroup = ribbonTab.getGroup(groupId);
234        
235        if (itemId != null)
236        {
237            if (!ribbonGroup.hasRibbonElement(controlId))
238            {
239                ribbonGroup.addRibbonElement(new Menu(controlId, new I18nizableText(controlId.substring(controlId.lastIndexOf(".") + 1))));
240            }
241            
242            RibbonElement ribbonControl = ribbonGroup.getRibbonElement(controlId);
243            
244            if (ribbonControl instanceof Menu)
245            {
246                Menu menu = (Menu) ribbonControl;
247                
248                if (subItemId == null)
249                {
250                    menu.addItem(itemId);
251                }
252                else
253                {
254                    if (!menu.hasMenu(itemId))
255                    {
256                        menu.addMenu(new Menu(itemId, new I18nizableText(itemId.substring(itemId.lastIndexOf(".") + 1))));
257                    }
258                    
259                    Menu subMenu = menu.getMenu(itemId);
260                    subMenu.addItem(subItemId);
261                }
262            }
263        }
264        else
265        {
266            ribbonGroup.addRibbonElement(new ParameterControl(controlId));
267        }
268    }
269    
270    private void _saxMenuIcons (Menu menu, AttributesImpl attrs)
271    {
272        String label = _i18nUtils.translate(menu.getLabel());
273        
274        for (Map.Entry<Pattern, String> entry : _glyphAssociations.entrySet())
275        {
276            Pattern pattern = entry.getKey();
277            if (pattern.matcher(label).matches())
278            {
279                attrs.addCDATAAttribute("iconGlyph", entry.getValue());
280                return;
281            }
282        }
283    }
284    
285    class RibbonTab
286    {
287        private I18nizableText _label;
288        private String _id;
289        private Map<String, RibbonGroup> _groups;
290        
291        public RibbonTab(String id, I18nizableText label)
292        {
293            _id = id;
294            _label = label;
295            _groups = new HashMap<>();
296        }
297        
298        String getId ()
299        {
300            return _id;
301        }
302        
303        I18nizableText getLabel ()
304        {
305            return _label;
306        }
307        
308        Collection<RibbonGroup> getGroups ()
309        {
310            return _groups.values();
311        }
312        
313        void addGroup (RibbonGroup group)
314        {
315            _groups.put(group.getId(), group);
316        }
317        
318        boolean hasGroup (String groupId)
319        {
320            return _groups.containsKey(groupId);
321        }
322        
323        RibbonGroup getGroup (String groupId)
324        {
325            return _groups.get(groupId);
326        }
327    }
328    
329    class RibbonGroup 
330    {
331        private String _id;
332        private I18nizableText _label;
333        private Map<String, RibbonElement> _ribbonElts;
334        
335        public RibbonGroup(String id, I18nizableText label)
336        {
337            _id = id;
338            _label = label;
339            _ribbonElts = new HashMap<>();
340        }
341        
342        String getId()
343        {
344            return _id;
345        }
346        
347        I18nizableText getLabel ()
348        {
349            return _label;
350        }
351        
352        void addRibbonElement (RibbonElement control)
353        {
354            _ribbonElts.put(control.getId(), control);
355        }
356        
357        boolean hasRibbonElement (String controlId)
358        {
359            return _ribbonElts.containsKey(controlId);
360        }
361        
362        RibbonElement getRibbonElement (String controlId)
363        {
364            return _ribbonElts.get(controlId);
365        }
366        
367        Collection<RibbonElement> getRibbonElements ()
368        {
369            return _ribbonElts.values();
370        }
371    }
372    
373    interface RibbonElement
374    {
375        public String getId();
376    }
377    
378    class Menu implements RibbonElement
379    {
380        private String _id;
381        private I18nizableText _label;
382        private List<String> _items;
383        private Map<String, Menu> _menus;
384        
385        public Menu(String id, I18nizableText label)
386        {
387            _id = id;
388            _label = label;
389            _items = new ArrayList<>();
390            _menus = new HashMap<>();
391        }
392        
393        @Override
394        public String getId()
395        {
396            return _id;
397        }
398        
399        I18nizableText getLabel ()
400        {
401            return _label;
402        }
403        
404        void addItem (String id)
405        {
406            _items.add(id);
407        }
408        
409        void addMenu (Menu menu)
410        {
411            _menus.put(menu.getId(), menu);
412        }
413        
414        boolean hasMenu (String menuId)
415        {
416            return _menus.containsKey(menuId);
417        }
418        
419        Menu getMenu (String menuId)
420        {
421            return _menus.get(menuId);
422        }
423        
424        List<String> getItems ()
425        {
426            return _items;
427        }
428        
429        Collection<Menu> getMenus()
430        {
431            return _menus.values();
432        }
433    }
434    
435    class ParameterControl implements RibbonElement
436    {
437        private String _id;
438        public ParameterControl(String id)
439        {
440            _id = id;
441        }
442        
443        @Override
444        public String getId()
445        {
446            return _id;
447        }
448    }
449}