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