001/* 002 * Copyright 2023 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.cms.transformation; 017 018import java.util.HashMap; 019import java.util.Map; 020 021import org.apache.cocoon.xml.AttributesImpl; 022import org.apache.cocoon.xml.XMLUtils; 023import org.xml.sax.Attributes; 024import org.xml.sax.ContentHandler; 025import org.xml.sax.Locator; 026import org.xml.sax.SAXException; 027 028import org.ametys.core.util.I18nUtils; 029import org.ametys.runtime.config.Config; 030import org.ametys.runtime.i18n.I18nizableText; 031import org.ametys.runtime.model.Model; 032import org.ametys.runtime.model.ModelItem; 033import org.ametys.runtime.model.type.DataContext; 034import org.ametys.runtime.model.type.ElementType; 035 036/** 037 * ContentHandler able to interpret configuration and transform it to XML value. 038 * 039 * For example it will transform an element with an attribute type="i18n" to the translated message. 040 * 041 * Currently interpretation are made for i18n and config. 042 * 043 * In the case of i18n, the translated message will be processed by a parser, allowing to provide rich text value through configuration. 044 */ 045public class Configuration2XMLValuesTransformer implements ContentHandler 046{ 047 private DataContext _context; 048 private I18nUtils _i18nUtils; 049 private String _configType; 050 private ContentHandler _contentHandler; 051 052 private StringBuilder _charBuffer; 053 private Map<String, Object> _configTypeParams; 054 private boolean _transformToDocbook; 055 056 /** 057 * Construct a content handler able to interpret configuration element. 058 * 059 * For example, the text content of an element with attribute type="i18n" will be replace by the corresponding translated message 060 * 061 * @param contentHandler sax event will be forwarded to this content handler after possible interpretation 062 * @param context the data context to use when interpreting the input 063 * @param i18nUtils the I18nUtils component. Use for translation 064 */ 065 public Configuration2XMLValuesTransformer(ContentHandler contentHandler, DataContext context, I18nUtils i18nUtils) 066 { 067 _contentHandler = contentHandler; 068 _context = context; 069 _i18nUtils = i18nUtils; 070 } 071 072 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException 073 { 074 if ("true".equals(atts.getValue("transform-to-docbook"))) 075 { 076 _transformToDocbook = true; 077 078 _charBuffer = new StringBuilder(); 079 } 080 081 // local reference in case we need to edit the attributes 082 Attributes attrs = atts; 083 if (_is18n(atts)) 084 { 085 _configType = "i18n"; 086 _configTypeParams = new HashMap<>(); 087 088 _configTypeParams.put("catalogue", atts.getValue("catalogue")); 089 090 _charBuffer = new StringBuilder(); 091 092 // clean attributes 093 AttributesImpl attrsCopy = new AttributesImpl(atts); 094 attrsCopy.removeAttribute("i18n"); 095 attrsCopy.removeAttribute("type"); 096 attrsCopy.removeAttribute("catalogue"); 097 098 attrs = attrsCopy; 099 _contentHandler.startElement(uri, localName, qName, attrs); 100 } 101 else if (_isConfig(atts)) 102 { 103 _configType = "config"; 104 105 _charBuffer = new StringBuilder(); 106 107 // clean attributes 108 AttributesImpl attrsCopy = new AttributesImpl(atts); 109 attrsCopy.removeAttribute("type"); 110 111 attrs = attrsCopy; 112 // Do not start element, element will be created in the end element 113 } 114 else 115 { 116 _contentHandler.startElement(uri, localName, qName, attrs); 117 } 118 } 119 120 private boolean _is18n(Attributes atts) 121 { 122 return "true".equals(atts.getValue("i18n")) || "i18n".equals(atts.getValue("type")); 123 } 124 125 private boolean _isConfig(Attributes atts) 126 { 127 return "config".equals(atts.getValue("type")); 128 } 129 130 public void characters(char[] ch, int start, int length) throws SAXException 131 { 132 if (_configType != null || _transformToDocbook) 133 { 134 for (int i = start; i < start + length; i++) 135 { 136 _charBuffer.append(ch[i]); 137 } 138 } 139 else 140 { 141 _contentHandler.characters(ch, start, length); 142 } 143 } 144 145 public void endElement(String uri, String localName, String qName) throws SAXException 146 { 147 if (_configType != null) 148 { 149 switch (_configType) 150 { 151 case "i18n": 152 _configType = null; 153 String translation = _i18nUtils.translate(new I18nizableText((String) _configTypeParams.get("catalogue"), _charBuffer.toString()), _context.getLocale().getLanguage()); 154 155 if (translation != null) 156 { 157 if (_transformToDocbook) 158 { 159 saxStringAsDockbook(translation); 160 } 161 else 162 { 163 _contentHandler.characters(translation.toCharArray(), 0, translation.length()); 164 } 165 } 166 167 _contentHandler.endElement(uri, localName, qName); 168 break; 169 case "config": 170 _configType = null; 171 172 Model configModel = Config.getModel(); 173 if (configModel != null) 174 { 175 String configId = _charBuffer.toString(); 176 ModelItem configDefinition = configModel.getChild(configId); 177 if (configDefinition != null && configDefinition instanceof ElementType elementType) 178 { 179 elementType.valueToSAX(_contentHandler, localName, Config.getInstance().getValue(configId), _context); 180 } 181 else 182 { 183 throw new SAXException("No config element is defined for id " + configId + "."); 184 } 185 } 186 break; 187 default: 188 throw new SAXException("Configuration type " + _configType + " is not supported."); 189 } 190 } 191 else 192 { 193 // in the case where we want to transform raw data 194 if (_transformToDocbook) 195 { 196 saxStringAsDockbook(_charBuffer.toString()); 197 } 198 _contentHandler.endElement(uri, localName, qName); 199 } 200 } 201 202 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException 203 { 204 if (_configType != null) 205 { 206 for (int i = start; i < start + length; i++) 207 { 208 _charBuffer.append(ch[i]); 209 } 210 } 211 else 212 { 213 if (_transformToDocbook) 214 { 215 saxStringAsDockbook(_charBuffer.toString()); 216 } 217 _contentHandler.ignorableWhitespace(ch, start, length); 218 } 219 } 220 221 private void saxStringAsDockbook(String string) throws SAXException 222 { 223 _transformToDocbook = false; 224 225 _contentHandler.startPrefixMapping("", "http://docbook.org/ns/docbook"); 226 _contentHandler.startPrefixMapping("xlink", "http://www.w3.org/1999/xlink"); 227 _contentHandler.startPrefixMapping("html", "http://www.w3.org/1999/xhtml"); 228 229 AttributesImpl atts = new AttributesImpl(); 230 atts.addCDATAAttribute("version", "5.0"); 231 XMLUtils.startElement(_contentHandler, "article", atts); 232 XMLUtils.createElement(_contentHandler, "para", string); 233 XMLUtils.endElement(_contentHandler, "article"); 234 235 _contentHandler.endPrefixMapping(""); 236 _contentHandler.endPrefixMapping("xlink"); 237 _contentHandler.endPrefixMapping("html"); 238 } 239 240 public void setDocumentLocator(Locator locator) 241 { 242 // ignore 243 } 244 245 public void startDocument() throws SAXException 246 { 247 // ignore 248 } 249 250 public void endDocument() throws SAXException 251 { 252 // ignore 253 } 254 255 public void startPrefixMapping(String prefix, String uri) throws SAXException 256 { 257 // ignore 258 } 259 260 public void endPrefixMapping(String prefix) throws SAXException 261 { 262 // ignore 263 } 264 265 public void processingInstruction(String target, String data) throws SAXException 266 { 267 // ignore 268 } 269 270 public void skippedEntity(String name) throws SAXException 271 { 272 // ignore 273 } 274 275}