001/* 002 * Copyright 2020 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.data; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.Map; 022 023import org.apache.avalon.framework.component.Component; 024import org.apache.avalon.framework.context.ContextException; 025import org.apache.avalon.framework.context.Contextualizable; 026import org.apache.cocoon.Constants; 027import org.apache.cocoon.environment.Context; 028import org.xml.sax.Attributes; 029import org.xml.sax.SAXException; 030import org.xml.sax.helpers.DefaultHandler; 031 032/** 033 * Factory for the transformer that imports the rich text metadata (attachments, semantic annotations, ...) from docbook. 034 */ 035public class RichTextImportMetadataHandlerFactory implements Component, Contextualizable 036{ 037 /** Avalon role. */ 038 public static final String ROLE = RichTextImportMetadataHandlerFactory.class.getName(); 039 private Context _cocoonContext; 040 041 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 042 { 043 _cocoonContext = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 044 } 045 046 /** 047 * Creates a handler to import the rich text's metadata 048 * @param richText the rich text 049 * @param files the attachments of this rich text 050 * @return the created handler 051 */ 052 public RichTextImportMetadataHandler createHandler(RichText richText, Map<String, InputStream> files) 053 { 054 return new RichTextImportMetadataHandler(richText, files); 055 } 056 057 /** 058 * This transformer imports the rich text metadata (attachments, semantic annotations, ...) from docbook. 059 */ 060 public class RichTextImportMetadataHandler extends DefaultHandler 061 { 062 private static final String __ATTACHMENT_TAG_NAME = "imagedata"; 063 private static final String __ATTACHMENT_TYPE_ATTRIBUTE_NAME = "type"; 064 private static final String __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE = "local"; 065 066 private static final String __ANNOTATION_TAG_NAME = "phrase"; 067 private static final String __ANNOTATION_NAME_ATTRIBUTE_NAME = "role"; 068 private static final String __ANNOTATION_CLASS_ATTRIBUTE_NAME = "class"; 069 private static final String __ANNOTATION_CLASS_ATTRIBUTE_VALUE = "semantic"; 070 071 072 private RichText _richText; 073 private Map<String, InputStream> _files; 074 075 private boolean _isCurrentlyInAnnotation; 076 private String _currentAnnotationName; 077 private StringBuilder _currentAnnotationValue; 078 private int _cptrElementsInsideCurrentAnnotation; 079 080 /** 081 * Creates a handler to import the rich text's metadata 082 * @param richText the rich text 083 * @param files the attachments of this rich text 084 */ 085 public RichTextImportMetadataHandler(RichText richText, Map<String, InputStream> files) 086 { 087 _richText = richText; 088 _files = files; 089 } 090 091 @Override 092 public void startDocument() throws SAXException 093 { 094 // Remove all existing attachments from the rich text. 095 _richText.removeAttachments(); 096 097 // Remove all existing annotations from the rich text. 098 _richText.removeAllAnnotations(); 099 100 super.startDocument(); 101 } 102 103 @Override 104 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException 105 { 106 // A new attachment starts being saxed 107 String type = attrs.getValue(__ATTACHMENT_TYPE_ATTRIBUTE_NAME); 108 if (__ATTACHMENT_TAG_NAME.equals(loc) && __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE.equals(type)) 109 { 110 _processAttachment(attrs); 111 } 112 113 // A new semantic annotation starts being saxed 114 String clazz = attrs.getValue(__ANNOTATION_CLASS_ATTRIBUTE_NAME); 115 String annotationName = attrs.getValue(__ANNOTATION_NAME_ATTRIBUTE_NAME); 116 if (__ANNOTATION_TAG_NAME.equals(loc) && __ANNOTATION_CLASS_ATTRIBUTE_VALUE.equals(clazz) && annotationName != null) 117 { 118 _processAnnotation(attrs); 119 } 120 else if (_isCurrentlyInAnnotation) 121 { 122 // A new element is being SAXed inside the current annotation 123 _cptrElementsInsideCurrentAnnotation++; 124 } 125 126 super.startElement(uri, loc, raw, attrs); 127 } 128 129 private void _processAttachment(Attributes attrs) throws SAXException 130 { 131 // file reference is of the form ownerId@dataName;fileName 132 String fileRefAttribute = attrs.getValue("fileref"); 133 int indexOfFilenameSeparator = fileRefAttribute.lastIndexOf(';'); 134 String filename = fileRefAttribute.substring(indexOfFilenameSeparator + 1); 135 136 if (indexOfFilenameSeparator == -1) 137 { 138 throw new IllegalArgumentException("A local image from inline editor should have an data-ametys-src attribute of the form <protocol>://<protocol-specific-part>;<filename> : " + fileRefAttribute); 139 } 140 141 if (_files.containsKey(filename)) 142 { 143 try 144 { 145 NamedResource attachment = new NamedResource(); 146 String mimeType = _cocoonContext.getMimeType(filename.toLowerCase()); 147 attachment.setMimeType(mimeType); 148 attachment.setFilename(filename); 149 attachment.setInputStream(_files.get(filename)); 150 _richText.addAttachment(attachment); 151 } 152 catch (IOException e) 153 { 154 throw new SAXException("Unable to process the attachment '" + filename + "'. An error occured while setting its content", e); 155 } 156 } 157 } 158 159 private void _processAnnotation(Attributes attrs) 160 { 161 _isCurrentlyInAnnotation = true; 162 _currentAnnotationName = attrs.getValue(__ANNOTATION_NAME_ATTRIBUTE_NAME); 163 _currentAnnotationValue = new StringBuilder(); 164 _cptrElementsInsideCurrentAnnotation = 0; 165 } 166 167 @Override 168 public void characters(char[] ch, int start, int length) throws SAXException 169 { 170 if (_isCurrentlyInAnnotation) 171 { 172 _currentAnnotationValue.append(ch, start, length); 173 } 174 175 super.characters(ch, start, length); 176 } 177 178 @Override 179 public void endElement(String uri, String loc, String raw) throws SAXException 180 { 181 if (_isCurrentlyInAnnotation) 182 { 183 if (_cptrElementsInsideCurrentAnnotation == 0) 184 { 185 // When the semantic annotation is fully saxed, add it to the rich text 186 _richText.addAnnotations(_currentAnnotationName, _currentAnnotationValue.toString()); 187 _isCurrentlyInAnnotation = false; 188 } 189 else 190 { 191 _cptrElementsInsideCurrentAnnotation--; 192 } 193 } 194 195 super.endElement(uri, loc, raw); 196 } 197 } 198}