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.plugins.repository.metadata.ModifiableRichText; 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 ModifiableRichText richText = (ModifiableRichText) parentContextParameters.get("richText"); 052 053 if (richText != null) 054 { 055 // Remove all existing annotations from the rich text node. 056 richText.removeAnnotations(); 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 final ModifiableRichText _richText; 124 private final List<AbstractSaxedSemanticAnnotation> _saxedSemanticAnnotations = new ArrayList<>(); 125 126 127 public SemanticAnnotationsHolder(ModifiableRichText richText) 128 { 129 this._richText = richText; 130 } 131 132 public void startElement() 133 { 134 for (AbstractSaxedSemanticAnnotation saxedSemanticAnnotation : _saxedSemanticAnnotations) 135 { 136 saxedSemanticAnnotation.startElement(); 137 } 138 } 139 140 public void endElement() throws SAXException 141 { 142 for (Iterator<AbstractSaxedSemanticAnnotation> it = _saxedSemanticAnnotations.iterator(); it.hasNext();) 143 { 144 AbstractSaxedSemanticAnnotation saxedSemanticAnnotation = it.next(); 145 try 146 { 147 saxedSemanticAnnotation.endElement(); 148 // Delete (finished) saxedSemanticAnnotation 149 if (saxedSemanticAnnotation.hasFinished()) 150 { 151 it.remove(); 152 } 153 } 154 catch (Exception e) 155 { 156 String annotationName = saxedSemanticAnnotation.getCurrentAnnotationName(); 157 throw new SAXException("Unable to save semantic annotation " + annotationName + " in repository", e); 158 } 159 } 160 } 161 162 public void characters(char[] ch, int start, int length) 163 { 164 for (AbstractSaxedSemanticAnnotation saxedSemanticAnnotation : _saxedSemanticAnnotations) 165 { 166 saxedSemanticAnnotation.characters(ch, start, length); 167 } 168 } 169 170 /** 171 * Add a new semantic annotation in the holder 172 * @param annotationName The name of the semantic annotation 173 */ 174 public void newSaxedSemanticAnnotation(String annotationName) 175 { 176 if (_richText != null) 177 { 178 AbstractSaxedSemanticAnnotation saxedSemanticAnnotation = new AbstractSaxedSemanticAnnotation(annotationName) 179 { 180 /** 181 * A SaxedSemanticAnnotation persists itself to JCR on its endElement. 182 * @see org.ametys.cms.transformation.htmledition.SemanticAnnotationsEditionHandler.AbstractSaxedSemanticAnnotation#onAnnotationReady() 183 */ 184 @Override 185 public void onAnnotationReady() throws Exception 186 { 187 // When the semAnnotation is fully saxed, persist it to JCR 188 _richText.addAnnotation(getCurrentAnnotationName(), getCurrentAnnotationValue().toString()); 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}