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}