001/* 002 * Copyright 2013 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 */ 016 017package org.ametys.cms.transformation.htmledition; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.cocoon.components.ContextHelper; 025import org.apache.cocoon.environment.ObjectModelHelper; 026import org.xml.sax.Attributes; 027import org.xml.sax.SAXException; 028 029import org.ametys.cms.data.RichText; 030import org.ametys.plugins.repository.metadata.ModifiableRichText; 031 032/** 033 * This transformer extracts semantic annotation from the incoming HTML for further processing. 034 * Appends all text nodes inside <span annotation="xxx"< element. 035 */ 036public class SemanticAnnotationsEditionHandler extends AbstractHTMLEditionHandler 037{ 038 private static final String __ANNOTATION_TAG_NAME = "span"; 039 private static final String __ANNOTATION_ATTRIBUTE = "annotation"; 040 041 SemanticAnnotationsHolder _annotationsHolder; 042 043 /** 044 * When document starts, a holder of semanticAnnotations is created 045 * @see org.ametys.cms.transformation.htmledition.AbstractHTMLEditionHandler#startDocument() 046 */ 047 @Override 048 public void startDocument() throws SAXException 049 { 050 Map objectModel = ContextHelper.getObjectModel(_context); 051 Map parentContextParameters = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 052 Object richTextObject = parentContextParameters.get("richText"); 053 054 if (richTextObject != null) 055 { 056 if (richTextObject instanceof RichText) 057 { 058 RichText richText = (RichText) richTextObject; 059 060 // Remove all existing annotations from the rich text node. 061 richText.removeAllAnnotations(); 062 // Create a holder to write the annotations to. 063 _annotationsHolder = new SemanticAnnotationsHolder(richText); 064 } 065 else if (richTextObject instanceof ModifiableRichText) 066 { 067 ModifiableRichText richText = (ModifiableRichText) richTextObject; 068 069 // Remove all existing annotations from the rich text node. 070 richText.removeAnnotations(); 071 // Create a holder to write the annotations to. 072 _annotationsHolder = new SemanticAnnotationsHolderOld(richText); 073 } 074 } 075 076 super.startDocument(); 077 } 078 079 /** 080 * Dispatch the startElement event to semanticAnnotations. Add a new annotation in the holder if the element matches a semantic annotation element 081 * @see org.ametys.cms.transformation.htmledition.AbstractHTMLEditionHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) 082 */ 083 @Override 084 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException 085 { 086 if (_annotationsHolder != null) 087 { 088 // Existing semantic annotations continue being saxed 089 _annotationsHolder.startElement(); 090 091 // A new semantic annotation starts being saxed 092 if (__ANNOTATION_TAG_NAME.equals(loc) && attrs.getValue(__ANNOTATION_ATTRIBUTE) != null) 093 { 094 // We will now track its annotationValue through the SAX events 095 _annotationsHolder.newSaxedSemanticAnnotation(attrs.getValue(__ANNOTATION_ATTRIBUTE)); 096 } 097 } 098 super.startElement(uri, loc, raw, attrs); 099 } 100 101 /** 102 * Dispatch the characters event to semanticAnnotations. 103 * @see org.ametys.cms.transformation.htmledition.AbstractHTMLEditionHandler#characters(char[], int, int) 104 */ 105 @Override 106 public void characters(char[] ch, int start, int length) throws SAXException 107 { 108 if (_annotationsHolder != null) 109 { 110 _annotationsHolder.characters(ch, start, length); 111 } 112 113 super.characters(ch, start, length); 114 } 115 116 /** 117 * Dispatch the endElement event to semanticAnnotations. 118 * @see org.ametys.cms.transformation.htmledition.AbstractHTMLEditionHandler#characters(char[], int, int) 119 */ 120 @Override 121 public void endElement(String uri, String loc, String raw) throws SAXException 122 { 123 if (_annotationsHolder != null) 124 { 125 _annotationsHolder.endElement(); 126 } 127 128 super.endElement(uri, loc, raw); 129 } 130 131 /** 132 * A placeholder for saxedSemanticAnnotations 133 * Maintains a list of the saxedSemanticAnnotations. 134 * Dispatch SAX events to each saxedSemanticAnnotation. 135 */ 136 private class SemanticAnnotationsHolder 137 { 138 protected List<AbstractSaxedSemanticAnnotation> _saxedSemanticAnnotations = new ArrayList<>(); 139 RichText _richText; 140 141 public SemanticAnnotationsHolder(RichText richText) 142 { 143 _richText = richText; 144 } 145 146 public void startElement() 147 { 148 for (AbstractSaxedSemanticAnnotation saxedSemanticAnnotation : _saxedSemanticAnnotations) 149 { 150 saxedSemanticAnnotation.startElement(); 151 } 152 } 153 154 public void endElement() throws SAXException 155 { 156 for (Iterator<AbstractSaxedSemanticAnnotation> it = _saxedSemanticAnnotations.iterator(); it.hasNext();) 157 { 158 AbstractSaxedSemanticAnnotation saxedSemanticAnnotation = it.next(); 159 try 160 { 161 saxedSemanticAnnotation.endElement(); 162 // Delete (finished) saxedSemanticAnnotation 163 if (saxedSemanticAnnotation.hasFinished()) 164 { 165 it.remove(); 166 } 167 } 168 catch (Exception e) 169 { 170 String annotationName = saxedSemanticAnnotation.getCurrentAnnotationName(); 171 throw new SAXException("Unable to save semantic annotation " + annotationName + " in repository", e); 172 } 173 } 174 } 175 176 public void characters(char[] ch, int start, int length) 177 { 178 for (AbstractSaxedSemanticAnnotation saxedSemanticAnnotation : _saxedSemanticAnnotations) 179 { 180 saxedSemanticAnnotation.characters(ch, start, length); 181 } 182 } 183 184 /** 185 * Add a new semantic annotation in the holder 186 * @param annotationName The name of the semantic annotation 187 */ 188 public void newSaxedSemanticAnnotation(String annotationName) 189 { 190 if (_richText != null) 191 { 192 AbstractSaxedSemanticAnnotation saxedSemanticAnnotation = new AbstractSaxedSemanticAnnotation(annotationName) 193 { 194 /** 195 * A SaxedSemanticAnnotation persists itself to JCR on its endElement. 196 * @see org.ametys.cms.transformation.htmledition.SemanticAnnotationsEditionHandler.AbstractSaxedSemanticAnnotation#onAnnotationReady() 197 */ 198 @Override 199 public void onAnnotationReady() throws Exception 200 { 201 // When the semAnnotation is fully saxed, persist it to JCR 202 _richText.addAnnotations(getCurrentAnnotationName(), getCurrentAnnotationValue().toString()); 203 } 204 }; 205 206 _saxedSemanticAnnotations.add(saxedSemanticAnnotation); 207 } 208 } 209 } 210 211 /** 212 * A semantic annotation defined through a serie of SAX events. 213 * The abstract onAnnotationReady() method takes care of the annotation as soon as it is fully SAXed. 214 */ 215 private abstract class AbstractSaxedSemanticAnnotation 216 { 217 private final String _currentAnnotationName; 218 private int _cptrElementsInsideSemanticAnnotation; 219 private final StringBuilder _currentAnnotationValue; 220 private boolean _hasFinished; 221 222 public AbstractSaxedSemanticAnnotation(String annotationName) 223 { 224 this._currentAnnotationName = annotationName; 225 this._cptrElementsInsideSemanticAnnotation = 0; 226 this._currentAnnotationValue = new StringBuilder(); 227 this._hasFinished = false; 228 } 229 230 public void startElement() 231 { 232 _cptrElementsInsideSemanticAnnotation++; 233 } 234 235 public void endElement() throws Exception 236 { 237 if (_cptrElementsInsideSemanticAnnotation == 0) 238 { 239 onAnnotationReady(); 240 _hasFinished = true; 241 } 242 else 243 { 244 _cptrElementsInsideSemanticAnnotation--; 245 } 246 } 247 248 public abstract void onAnnotationReady() throws Exception; 249 250 public void characters(char[] ch, int start, int length) 251 { 252 _currentAnnotationValue.append(ch, start, length); 253 } 254 255 public String getCurrentAnnotationName() 256 { 257 return _currentAnnotationName; 258 } 259 260 261 public StringBuilder getCurrentAnnotationValue() 262 { 263 return _currentAnnotationValue; 264 } 265 266 267 public boolean hasFinished() 268 { 269 return _hasFinished; 270 } 271 } 272 /** 273 * A placeholder for saxedSemanticAnnotations 274 * Maintains a list of the saxedSemanticAnnotations. 275 * Dispatch SAX events to each saxedSemanticAnnotation. 276 */ 277 @Deprecated 278 private class SemanticAnnotationsHolderOld extends SemanticAnnotationsHolder 279 { 280 @SuppressWarnings("hiding") 281 final ModifiableRichText _richText; 282 283 public SemanticAnnotationsHolderOld(ModifiableRichText richText) 284 { 285 super(null); 286 this._richText = richText; 287 } 288 289 @Override 290 public void newSaxedSemanticAnnotation(String annotationName) 291 { 292 if (_richText != null) 293 { 294 AbstractSaxedSemanticAnnotation saxedSemanticAnnotation = new AbstractSaxedSemanticAnnotation(annotationName) 295 { 296 /** 297 * A SaxedSemanticAnnotation persists itself to JCR on its endElement. 298 * @see org.ametys.cms.transformation.htmledition.SemanticAnnotationsEditionHandler.AbstractSaxedSemanticAnnotation#onAnnotationReady() 299 */ 300 @Override 301 public void onAnnotationReady() throws Exception 302 { 303 // When the semAnnotation is fully saxed, persist it to JCR 304 _richText.addAnnotation(getCurrentAnnotationName(), getCurrentAnnotationValue().toString()); 305 } 306 }; 307 _saxedSemanticAnnotations.add(saxedSemanticAnnotation); 308 } 309 } 310 } 311}