Projekt

Obecné

Profil

Stáhnout (38.6 KB) Statistiky
| Větev: | Tag: | Revize:
1 eff8ec56 Vojtěch Bartička
using Core.Contexts;
2
using Core.Entities;
3
using Models.Annotations;
4
using Models.Enums;
5
using Serilog;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9
using System.Text;
10
using System.Threading.Tasks;
11 6bdb3d95 Vojtěch Bartička
using Microsoft.EntityFrameworkCore;
12 a6675a6d Vojtěch Bartička
using AutoMapper;
13
using Models.Tags;
14 3c185841 Vojtěch Bartička
using Ganss.XSS;
15
using HtmlAgilityPack;
16
using System.Text.RegularExpressions;
17 553486ad Vojtěch Bartička
using Newtonsoft.Json;
18 8dc25caa Vojtěch Bartička
using Core.GraphUtils;
19 eff8ec56 Vojtěch Bartička
20
namespace Core.Services.AnnotationService
21
{
22
    public class AnnotationServiceEF : IAnnotationService
23
    {
24
        private readonly DatabaseContext context;
25
        private readonly ILogger logger;
26 a6675a6d Vojtěch Bartička
        private readonly IMapper mapper;
27 eff8ec56 Vojtěch Bartička
28 0a9f9349 Vojtěch Bartička
        private const string TAG_ID_ATTRIBUTE_NAME = "aswi-tag-id";
29 c9762683 Vojtěch Bartička
        private const string TAG_INSTANCE_ATTRIBUTE_NAME = "aswi-tag-instance";
30 553486ad Vojtěch Bartička
        private const string TAG_EF_ID_ATTRIBUTE_NAME = "aswi-tag-ef-id";
31 0a9f9349 Vojtěch Bartička
32 a6675a6d Vojtěch Bartička
        public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper)
33 eff8ec56 Vojtěch Bartička
        {
34
            this.context = context;
35
            this.logger = logger;
36 a6675a6d Vojtěch Bartička
            this.mapper = mapper;
37 eff8ec56 Vojtěch Bartička
        }
38
39
        public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId)
40
        {
41
            User addingUser = context.Users.Single(u => u.Id == clientUserId);
42
43
            // Check the documents exist
44 153d77a8 Vojtěch Bartička
            var documents = context.Documents.Where(d => request.DocumentIdList.Contains(d.Id)).ToList();
45
            if (documents.Count() != request.DocumentIdList.Count)
46 eff8ec56 Vojtěch Bartička
            {
47 153d77a8 Vojtěch Bartička
                logger.Information($"Received a non-existent Document ID when assigning documents to users");
48
                throw new InvalidOperationException($"{request.DocumentIdList.Count - documents.Count()} of the received documents do not exist");
49 eff8ec56 Vojtěch Bartička
            }
50
51 6bdb3d95 Vojtěch Bartička
            var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList();
52 153d77a8 Vojtěch Bartička
            foreach (var user in users)
53 eff8ec56 Vojtěch Bartička
            {
54 153d77a8 Vojtěch Bartička
                var userAnnotatedDocuments = context.Annotations.Where(a => a.User == user).Select(a => a.Document).ToList();
55
                foreach (var doc in documents)
56 eff8ec56 Vojtěch Bartička
                {
57 153d77a8 Vojtěch Bartička
                    if (userAnnotatedDocuments.Contains(doc))
58 eff8ec56 Vojtěch Bartička
                    {
59 153d77a8 Vojtěch Bartička
                        logger.Information($"User {user.Username} has already been assigned the document {doc.Id}, ignoring");
60
                        continue;
61 eff8ec56 Vojtěch Bartička
                    }
62
63
                    context.Annotations.Add(new Annotation()
64
                    {
65
                        User = user,
66
                        UserAssigned = addingUser,
67
                        DateAssigned = DateTime.Now,
68
                        DateLastChanged = DateTime.Now,
69 153d77a8 Vojtěch Bartička
                        Document = doc,
70 eff8ec56 Vojtěch Bartička
                        State = EState.NEW,
71
                        Note = ""
72
                    });
73
                }
74
            }
75
76
            context.SaveChanges();
77
        }
78 6bdb3d95 Vojtěch Bartička
79
        public AnnotationListResponse GetUserAnnotations(Guid userId)
80
        {
81
            var annotations = context.Annotations.Where(a => a.User.Id == userId).Include(a => a.Document).ToList();
82
            var documentIds = annotations.Select(a => a.Document.Id).ToList();
83
            var documents = context.Documents.Where(d => documentIds.Contains(d.Id));
84
            var infos = new List<AnnotationListInfo>();
85
86
            var annotationsDocuments = annotations.Zip(documents, (a, d) => new { Annotation = a, Document = d });
87
            foreach (var ad in annotationsDocuments)
88
            {
89
                infos.Add(new AnnotationListInfo()
90
                {
91
                    AnnotationId = ad.Annotation.Id,
92
                    DocumentName = ad.Document.Name,
93
                    State = ad.Annotation.State
94
                });
95
            }
96
97
            return new AnnotationListResponse()
98
            {
99
                Annotations = infos
100
            };
101
        }
102 a6675a6d Vojtěch Bartička
103 f2275185 Vojtěch Bartička
        public void AddNoteToAnnotation(Guid annotationId, Guid userId, ERole userRole, AddNoteToAnnotationRequest request)
104
        {
105
            Annotation annotation = null;
106
            try
107
            {
108
                annotation = context.Annotations.Include(a => a.User).First(a => a.Id == annotationId);
109
            }
110
            catch (Exception)
111
            {
112
                throw new InvalidOperationException("Annotation not found");
113
            }
114
115
            if (userRole < ERole.ADMINISTRATOR && annotation.User.Id != userId)
116
            {
117
                throw new UnauthorizedAccessException("User does not have access to this annotation");
118
            }
119
120
            annotation.Note = request.Note;
121
            context.SaveChanges();
122
        }
123
124
125 a6675a6d Vojtěch Bartička
        public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole)
126
        {
127 c2a89232 Vojtěch Bartička
            var annotation = context.Annotations
128
                .Where(a => a.Id == annotationId)
129
                .Include(a => a.User)
130
                .Include(a => a.Document).ThenInclude(d => d.Content)
131
                .First();
132 3c185841 Vojtěch Bartička
133 a6675a6d Vojtěch Bartička
            if (userRole < ERole.ADMINISTRATOR)
134
            {
135
                if (annotation.User.Id != userId)
136
                {
137
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
138
                }
139
            }
140
141 c2a89232 Vojtěch Bartička
            var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First();
142 a6675a6d Vojtěch Bartička
143 c2a89232 Vojtěch Bartička
            var tags = context.AnnotationTags.Where(at => at.Annotation.Id == annotationId)
144 553486ad Vojtěch Bartička
                .Include(at => at.Tag)
145
                .ThenInclude(t => t.Category)
146 c2a89232 Vojtěch Bartička
                .Include(at => at.SubTag)
147 3c89e947 Vojtěch Bartička
                .OrderBy(at => at.Position)
148 c2a89232 Vojtěch Bartička
                .ToList();
149
150 0a9f9349 Vojtěch Bartička
            List<TagInstanceInfo> tagInstanceInfos = new();
151 a6675a6d Vojtěch Bartička
            foreach (var tag in tags)
152
            {
153
                var tagInstance = mapper.Map<TagInstanceInfo>(tag);
154 0a9f9349 Vojtěch Bartička
                tagInstanceInfos.Add(tagInstance);
155 a6675a6d Vojtěch Bartička
            }
156
157 553486ad Vojtěch Bartička
            var docToRender = "";
158
            if (annotation.CachedDocumentHTML == "")
159
            {
160
                docToRender = FullPreprocessHTML(documentContent.Content, tags);
161
                annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
162
                annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
163
                annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
164
                annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
165 4e12f0cc Vojtěch Bartička
                annotation.CachedCSS = JsonConvert.SerializeObject(TagInstanceCSS);
166 553486ad Vojtěch Bartička
                annotation.ModifiedType = EModified.NONE;
167
                annotation.CachedDocumentHTML = docToRender;
168
                context.SaveChanges();
169
            }
170
            else
171
            {
172
                docToRender = annotation.CachedDocumentHTML;
173
                TagStartPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedStartPositions);
174
                TagStartLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedLengths);
175
                TagClosingPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingPositions);
176
                TagClosingLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingLengths);
177 26671569 Vojtěch Bartička
                TagInstanceCSS = JsonConvert.DeserializeObject<List<TagInstanceCSSInfo>>(annotation.CachedCSS);
178 553486ad Vojtěch Bartička
179
                // The annotation has been modified and we need to either add the new tag or remove the tag
180
                if (annotation.ModifiedType != EModified.NONE)
181
                {
182
                    if (annotation.ModifiedType == EModified.ADDED)
183
                    {
184
                        var lastModifiedTag = context.AnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First();
185
                        docToRender = PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags);
186
                    }
187
                    else if (annotation.ModifiedType == EModified.REMOVED)
188
                    {
189
                        docToRender = PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags);
190
                    }
191
192
                    annotation.ModifiedType = EModified.NONE;
193
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
194
                    annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
195
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
196
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
197
                    annotation.CachedDocumentHTML = docToRender;
198 26671569 Vojtěch Bartička
                    annotation.CachedCSS = JsonConvert.SerializeObject(TagInstanceCSS);
199 553486ad Vojtěch Bartička
                    context.SaveChanges();
200
                }
201
            }
202 0a9f9349 Vojtěch Bartička
203
            // We probably cannot use AutoMapper since we are dealing with too many different entities
204
            AnnotationInfo annotationInfo = new()
205
            {
206
                SourceDocumentContent = documentContent.Content,
207
                DocumentToRender = docToRender,
208
                TagStartPositions = TagStartPositions.ToArray(),
209
                TagLengths = TagStartLengths.ToArray(),
210
                Note = annotation.Note,
211
                State = annotation.State,
212
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
213 26671569 Vojtěch Bartička
                TagInstances = tagInstanceInfos,
214
                CSSInfo = TagInstanceCSS
215 0a9f9349 Vojtěch Bartička
            };
216
217 553486ad Vojtěch Bartička
            NodeDict.Clear();
218
219 a6675a6d Vojtěch Bartička
            return annotationInfo;
220
        }
221
222 0a9f9349 Vojtěch Bartička
        private List<int> TagStartPositions = new();
223
        private List<int> TagStartLengths = new();
224
        private List<int> TagClosingPositions = new();
225
        private List<int> TagClosingLengths = new();
226
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
227 4e12f0cc Vojtěch Bartička
        private List<TagInstanceCSSInfo> TagInstanceCSS = new();
228 0a9f9349 Vojtěch Bartička
229 553486ad Vojtěch Bartička
        /*
230
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
231
         */
232
233
234
        private string FullPreprocessHTML(string htmlSource, List<AnnotationTag> tags)
235 3c185841 Vojtěch Bartička
        {
236
            var docOriginal = new HtmlDocument();
237
            docOriginal.LoadHtml(htmlSource);
238
            var docToEdit = new HtmlDocument();
239
            docToEdit.LoadHtml(htmlSource);
240
241
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
242 553486ad Vojtěch Bartička
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
243 3c185841 Vojtěch Bartička
244 0a9f9349 Vojtěch Bartička
            int currentId = 0;
245 3c185841 Vojtěch Bartička
246 0a9f9349 Vojtěch Bartička
            FillNodeDict(descendantsOriginal, descendantsToEdit);
247
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
248 3c185841 Vojtěch Bartička
249 0a9f9349 Vojtěch Bartička
            WrapTextInSpan(descendantsOriginal, docToEdit);
250 3c185841 Vojtěch Bartička
251 553486ad Vojtěch Bartička
            int descCount = descendantsToEdit.Count;
252
253 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
254 3c185841 Vojtěch Bartička
            {
255 0a9f9349 Vojtěch Bartička
                int i = 0;
256
                List<HtmlNode> addedForSelection = new();
257 553486ad Vojtěch Bartička
                while (i < descCount)
258 3c185841 Vojtěch Bartička
                {
259 553486ad Vojtěch Bartička
                    for (; i < descCount; i++)
260 0a9f9349 Vojtěch Bartička
                    {
261
                        var node = descendantsToEdit.ElementAt(i);
262
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
263
                            node.ParentNode.Name == "style")
264
                        {
265
                            continue;
266
                        }
267
268 c9762683 Vojtěch Bartička
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
269
270
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
271
                        var end = TagClosingPositions[nodeId];
272 0a9f9349 Vojtěch Bartička
273
                        int selectionStart = tag.Position;
274
                        int selectionEnd = tag.Position + tag.Length;
275
276
                        if (selectionStart < end && selectionEnd > start)
277
                        {
278
                            if (selectionStart <= start && selectionEnd >= end)
279
                            {
280
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
281
                            }
282
                            else if (selectionStart <= start)
283
                            {
284
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
285
                            }
286
                            else if (selectionEnd >= end)
287
                            {
288
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
289
                            }
290
                            else
291
                            {
292
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
293
                            }
294 553486ad Vojtěch Bartička
                            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
295
                            descCount = descendantsToEdit.Count;
296 0a9f9349 Vojtěch Bartička
                            break;
297
                        }
298
                    }
299 3c185841 Vojtěch Bartička
                }
300
301
            }
302
303
            string docToRender = docToEdit.DocumentNode.OuterHtml;
304
            HtmlSanitizer sanitizer = new HtmlSanitizer();
305
            sanitizer.AllowedAttributes.Clear();
306 0a9f9349 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
307
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
308 c9762683 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add("end");
309
            sanitizer.AllowedAttributes.Add("start");
310
            sanitizer.AllowedAttributes.Add("class");
311 be4deff8 Vojtěch Bartička
            if (sanitizer.AllowedTags.Contains("script"))
312
            {
313
                sanitizer.AllowedTags.Remove("script");
314 553486ad Vojtěch Bartička
            }
315 c9762683 Vojtěch Bartička
            if (!sanitizer.AllowedTags.Contains("style"))
316
            {
317
                sanitizer.AllowedTags.Add("style");
318 be4deff8 Vojtěch Bartička
            }
319 4e12f0cc Vojtěch Bartička
            docToRender = sanitizer.Sanitize(docToRender);        
320
            GenerateCSS(tags);
321
            return docToRender;
322 0a9f9349 Vojtěch Bartička
        }
323
324
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
325
        {
326
            // full fill
327
            string textSelected = node.InnerText;
328
329
            var parentNode = node.ParentNode;
330
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
331
            parentNode.ChildNodes.RemoveAt(nodeIndex);
332
333
            EPosition markerPosition = EPosition.MARK_NONE;
334
            if (selectionEnd == end && selectionStart == start)
335
            {
336
                markerPosition = EPosition.MARK_LEFT_RIGHT;
337
            }
338
339 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
340 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
341
342
            return spanSelected;
343
        }
344
345
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
346
                                             AnnotationTag tag)
347
        {
348
            // partial fill, end gap
349
            string text = node.InnerText;
350
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
351
            string textSelected = text.Substring(0, selectionEnd - start);
352
353
            var parentNode = node.ParentNode;
354
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
355
            parentNode.ChildNodes.RemoveAt(nodeIndex);
356
357
            int spanSelectedStart = start;
358
            int spanAfterStart = start + textSelected.Length;
359
360 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
361 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
362
363 553486ad Vojtěch Bartička
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
364 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
365
366
            return new() { spanSelected, spanAfter };
367
        }
368
369
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
370
                                             AnnotationTag tag)
371
        {
372
            // partial fill, start gap
373
            string text = node.InnerText;
374
            string textBefore = text.Substring(0, selectionStart - start);
375
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
376
377
            var parentNode = node.ParentNode;
378
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
379
            parentNode.ChildNodes.RemoveAt(nodeIndex);
380
381
            int spanBeforeStart = start;
382
            int spanSelectedStart = start + textBefore.Length;
383
384 553486ad Vojtěch Bartička
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
385 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
386
387 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT);
388 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
389
390
            return new() { spanSelected, spanBefore };
391
        }
392
393
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
394
                                                 AnnotationTag tag)
395
        {
396
            // partial fill, start gap end gap
397
            string text = node.InnerText;
398
            string textBefore = text.Substring(0, selectionStart - start);
399
            string textAfter = text.Substring(selectionStart - start + tag.Length);
400
            string textSelected = text.Substring(selectionStart - start, tag.Length);
401
402
            var parentNode = node.ParentNode;
403
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
404
            parentNode.ChildNodes.RemoveAt(nodeIndex);
405
406
            int spanBeforeStart = start;
407
            int spanSelectedStart = start + textBefore.Length;
408
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
409
410 553486ad Vojtěch Bartička
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
411 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
412
413 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT_RIGHT);
414 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
415
416 553486ad Vojtěch Bartička
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
417 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
418
419
            return new() { spanSelected, spanBefore, spanAfter };
420
        }
421
422 553486ad Vojtěch Bartička
        private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE)
423 0a9f9349 Vojtěch Bartička
        {
424
            HtmlNode span = doc.CreateElement("span");
425
            span.InnerHtml = text;
426
            TagStartPositions.Add(startPosition);
427
            TagStartLengths.Add(0);
428
            TagClosingPositions.Add(startPosition + text.Length);
429
            TagClosingLengths.Add(0);
430 553486ad Vojtěch Bartička
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
431 0a9f9349 Vojtěch Bartička
432
            if (instanceId != null)
433
            {
434
                span.AddClass("annotation");
435 553486ad Vojtěch Bartička
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
436
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
437 0a9f9349 Vojtěch Bartička
438
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
439
                {
440
                    span.Attributes.Add("start", "1");
441
                }
442
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
443
                {
444
                    span.Attributes.Add("end", "1");
445
                }
446
            }
447
448
            return span;
449
        }
450
451
        private enum EPosition
452
        {
453
            MARK_LEFT = 5,
454
            MARK_RIGHT = 3,
455
            MARK_LEFT_RIGHT = 2,
456
            MARK_NONE = 0
457
        }
458
459
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
460
        {
461 ecf6d2e0 Vojtěch Bartička
            // Special case for non-html documents
462
            if (descendantsOriginal.Count() == 2)
463
            {
464
                var documentNode = descendantsOriginal.ElementAt(0);
465
                var childNode = descendantsOriginal.ElementAt(1);
466
                if (documentNode.Name == "#document" && childNode.Name == "#text")
467
                {
468
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
469
                    coveringSpan.InnerHtml = childNode.InnerHtml;
470
                    TagStartPositions.Add(childNode.InnerStartIndex);
471
                    TagStartLengths.Add(0);
472
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
473
                    TagClosingLengths.Add(0);
474
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
475
476
                    var parent = NodeDict[documentNode];
477
478
                    parent.ChildNodes.RemoveAt(0);
479
                    parent.ChildNodes.Add(coveringSpan);
480
481
                    return;
482
                }
483
            }
484
485 0a9f9349 Vojtěch Bartička
            foreach (var node in descendantsOriginal)
486
            {
487
                var originalNode = node;
488
                var toEditNode = NodeDict[node];
489
490
                if (originalNode.Name.Contains("#"))
491
                {
492
                    continue;
493
                }
494
                else
495
                {
496
                    bool onlyText = true;
497
                    bool onlySubtags = true;
498
499
                    foreach (var child in node.ChildNodes)
500
                    {
501
                        if (child.Name.Contains("#"))
502
                        {
503
                            onlySubtags = false;
504
                        }
505
                        else
506
                        {
507
                            onlyText = false;
508
                        }
509
                    }
510
511
                    if (onlyText || onlySubtags)
512
                    {
513
                        continue;
514
                    }
515
                    else
516
                    {
517
518
                        foreach (var child in node.ChildNodes)
519
                        {
520
                            if (child.Name.Contains("#text"))
521
                            {
522
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
523
                                coveringSpan.InnerHtml = child.InnerHtml;
524
                                TagStartPositions.Add(child.InnerStartIndex);
525
                                TagStartLengths.Add(0);
526
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
527
                                TagClosingLengths.Add(0);
528
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
529
530
                                var parent = NodeDict[node];
531
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
532
533
                                parent.ChildNodes.RemoveAt(index);
534
                                parent.ChildNodes.Insert(index, coveringSpan);
535
                            }
536
                        }
537
                    }
538
                }
539
            }
540
        }
541
542 4e12f0cc Vojtěch Bartička
        private void GenerateCSS(List<AnnotationTag> tags)
543 0a9f9349 Vojtěch Bartička
        {
544 4e12f0cc Vojtěch Bartička
            /*string inner = "span.annotation {border-bottom: 2px solid;}";
545
            inner += "span {line-height: 30px}\n";*/
546 0a9f9349 Vojtěch Bartička
547 8dc25caa Vojtěch Bartička
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
548 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
549
            {
550 8dc25caa Vojtěch Bartička
                var padding = (tagPaddingDict[tag] + 1) * 2;
551 4e12f0cc Vojtěch Bartička
                TagInstanceCSS.Add(new()
552
                {
553
                    InstanceId = tag.Instance,
554
                    Color = tag.Tag.Color,
555
                    Padding = padding
556
                });             
557 0a9f9349 Vojtěch Bartička
            }
558
        }
559
560
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
561
        {
562
            foreach (var node in descendantsOriginal)
563
            {
564
                var originalNode = node;
565
                var toEditNode = NodeDict[node];
566
567
                if (originalNode.Name.Contains("#"))
568
                {
569
                    continue;
570
                }
571
                else
572
                {
573
                    TagStartPositions.Add(originalNode.OuterStartIndex);
574
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
575
                    currentId = TagStartPositions.Count - 1;
576
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
577
578
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
579
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
580
                }
581
            }
582
        }
583
584
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
585
        {
586
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
587
            {
588
                Original = orig,
589
                ToEdit = toEdit
590
            });
591
            foreach (var node in zipped)
592
            {
593
                var originalNode = node.Original;
594
                var toEditNode = node.ToEdit;
595
                NodeDict.Add(originalNode, toEditNode);
596
            }
597 3c185841 Vojtěch Bartička
        }
598
599 553486ad Vojtěch Bartička
600
        /*
601
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
602
         */
603
604
        /*
605
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
606
         */
607
608
        private string PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTag> tags)
609
        {
610
            var docOriginal = new HtmlDocument();
611
            docOriginal.LoadHtml(htmlOriginal);
612
            var docToEdit = new HtmlDocument();
613
            docToEdit.LoadHtml(htmlToEdit);
614
615
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
616
617
            int i = 0;
618
            List<HtmlNode> addedForSelection = new();
619
620
            int descendantsCount = descendantsToEdit.Count();
621
            while (i < descendantsCount)
622
            {
623
                for (; i < descendantsCount; i++)
624
                {
625
                    var node = descendantsToEdit.ElementAt(i);
626
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
627 ecf6d2e0 Vojtěch Bartička
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
628 553486ad Vojtěch Bartička
                    {
629
                        continue;
630
                    }
631
632
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
633
634
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
635
                    var end = TagClosingPositions[nodeId];
636
637
                    int selectionStart = tagToAdd.Position;
638
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
639
640
                    if (selectionStart < end && selectionEnd > start)
641
                    {
642
                        if (selectionStart <= start && selectionEnd >= end)
643
                        {
644
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
645
                        }
646
                        else if (selectionStart <= start)
647
                        {
648
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
649
                        }
650
                        else if (selectionEnd >= end)
651
                        {
652
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
653
                        }
654
                        else
655
                        {
656
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
657
                        }
658
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
659
                        descendantsCount = descendantsToEdit.Count();
660
                        break;
661
                    }
662
                }
663
            }
664
665 4e12f0cc Vojtěch Bartička
            GenerateCSS(tags);
666 553486ad Vojtěch Bartička
            return docToEdit.DocumentNode.OuterHtml;
667
        }
668
669
670
        private string PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTag> tags)
671
        {
672
            var docOriginal = new HtmlDocument();
673
            docOriginal.LoadHtml(htmlOriginal);
674
            var docToEdit = new HtmlDocument();
675
            docToEdit.LoadHtml(htmlToEdit);
676
677
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
678
679
            int i = 0;
680
            int descendantsCount = descendantsToEdit.Count();
681
            while (i < descendantsCount)
682
            {
683
                for (; i < descendantsCount; i++)
684
                {
685
                    var node = descendantsToEdit.ElementAt(i);
686
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
687
                    {
688
                        continue;
689
                    }
690
                    else
691
                    {
692
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
693
                        {
694
                            continue;
695
                        }
696
697
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
698
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
699
                        node.Attributes.Remove("class");
700
                        node.Attributes.Remove("start");
701
                        node.Attributes.Remove("end");
702
703
                        /*var parent = node.ParentNode;
704
                        var contents = node.ChildNodes;
705
                        int index = parent.ChildNodes.IndexOf(node);
706
                        parent.ChildNodes.RemoveAt(index);
707
708
                        List<HtmlNode> newChildren = new();
709
                        for (int j = 0; j < index; j++)
710
                        {
711
                            newChildren.Add(parent.ChildNodes[j]);
712
                        }
713
                        for (int j = 0; j < contents.Count; j++)
714
                        {
715
                            newChildren.Add(contents[j]);
716
                        }
717
                        for (int j = index; j < parent.ChildNodes.Count; j++)
718
                        {
719
                            newChildren.Add(parent.ChildNodes[j]);
720
                        }
721
722
                        parent.ChildNodes.Clear();
723
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
724 58363b44 Vojtěch Bartička
725 553486ad Vojtěch Bartička
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
726
                        descendantsCount = descendantsToEdit.Count();
727
                        break;
728
                    }
729
                }
730
            }
731
732 4e12f0cc Vojtěch Bartička
            GenerateCSS(tags);
733 553486ad Vojtěch Bartička
            return docToEdit.DocumentNode.OuterHtml;
734
        }
735
736
        /*
737
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
738
         */
739
740
741 a6675a6d Vojtěch Bartička
        // TODO temporary
742
        private bool IsHtml(string text)
743
        {
744 0a9f9349 Vojtěch Bartička
            return text.Contains("<html>");
745 a6675a6d Vojtěch Bartička
        }
746 be4deff8 Vojtěch Bartička
747
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
748
        {
749
            var annotation = context.Annotations
750
               .Where(a => a.Id == annotationId)
751
               .Include(a => a.User)
752
               .Include(a => a.Document).ThenInclude(d => d.Content)
753
               .First();
754
755
            if (userRole < ERole.ADMINISTRATOR)
756
            {
757
                if (annotation.User.Id != userId)
758
                {
759
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
760
                }
761
            }
762
763 2f3821f0 Vojtěch Bartička
            if (annotation.State == EState.NEW)
764
            {
765
                annotation.State = EState.IN_PROGRESS;
766
            }
767
768 be4deff8 Vojtěch Bartička
            AnnotationTag annotationTag = new()
769
            {
770 553486ad Vojtěch Bartička
                Id = Guid.NewGuid(),
771 be4deff8 Vojtěch Bartička
                Annotation = annotation,
772
                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
773
                Length = request.Length,
774
                Position = request.Position,
775
                Note = ""
776
            };
777
778
            if (request.Type == ETagType.TAG)
779
            {
780
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
781
                annotationTag.SubTag = null;
782 15c88dc1 Vojtěch Bartička
783
                if (annotationTag.Tag.SentimentEnabled)
784
                {
785
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
786
                }
787 2f3821f0 Vojtěch Bartička
788 58363b44 Vojtěch Bartička
                // If for the same annotation exists a tag with same position and length and of the same type, ignore
789 2f3821f0 Vojtěch Bartička
                if (context.AnnotationTags.Any(at =>
790 58363b44 Vojtěch Bartička
                at.Position == annotationTag.Position &&
791
                at.Length == annotationTag.Length &&
792
                at.Annotation == annotation &&
793
                at.Tag == annotationTag.Tag))
794
                {
795
                    throw new InvalidOperationException("Duplicate tag");
796
                }
797
798 be4deff8 Vojtěch Bartička
            }
799
            else if (request.Type == ETagType.SUBTAG)
800
            {
801
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
802
                annotationTag.SubTag = subTag;
803
                annotationTag.Tag = subTag.Tag;
804 15c88dc1 Vojtěch Bartička
805
                if (annotationTag.SubTag.SentimentEnabled)
806
                {
807
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
808
                }
809 2f3821f0 Vojtěch Bartička
810
                if (context.AnnotationTags.Any(at =>
811 58363b44 Vojtěch Bartička
                at.Position == annotationTag.Position &&
812
                at.Length == annotationTag.Length &&
813
                at.Annotation == annotation &&
814
                at.Tag == annotationTag.Tag &&
815
                at.SubTag == annotationTag.SubTag))
816
                {
817
                    throw new InvalidOperationException("Duplicate tag");
818
                }
819 be4deff8 Vojtěch Bartička
            }
820
            else
821
            {
822
                throw new ArgumentException($"Unknown tag type {request.Type}");
823
            }
824
825 553486ad Vojtěch Bartička
            annotation.LastModifiedTagId = annotationTag.Id;
826
            annotation.ModifiedType = EModified.ADDED;
827
828 abf94f21 Lukáš Vlček
            context.AnnotationTags.Add(annotationTag);
829 be4deff8 Vojtěch Bartička
            context.SaveChanges();
830
        }
831 0f8d6304 Vojtěch Bartička
832
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
833
        {
834
            Annotation annotation = null;
835
            try
836
            {
837
                annotation = context.Annotations
838
                   .Where(a => a.Id == annotationId)
839
                   .Include(a => a.User)
840
                   .Include(a => a.Document).ThenInclude(d => d.Content)
841
                   .First();
842
843
            }
844
            catch (Exception ex)
845
            {
846
                throw new InvalidOperationException("Could not find annotation");
847
            }
848
849
850
            if (userRole < ERole.ADMINISTRATOR)
851
            {
852
                if (annotation.User.Id != loggedUserId)
853
                {
854
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
855
                }
856
            }
857
858
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
859
            {
860
                throw new InvalidOperationException("Could not find tag instance");
861
            }
862
863 553486ad Vojtěch Bartička
864
            var annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
865
            annotation.LastModifiedTagId = annotationTag.Id;
866
            annotation.ModifiedType = EModified.REMOVED;
867
868
            context.AnnotationTags.Remove(annotationTag);
869 0a9f9349 Vojtěch Bartička
870 0f8d6304 Vojtěch Bartička
            context.SaveChanges();
871
        }
872 15c88dc1 Vojtěch Bartička
873
        public void SetTagInstanceSentiment(Guid annotationId, Guid instanceId, Guid userId, ERole userRole, ETagSentiment sentiment)
874
        {
875
            Annotation annotation = null;
876
            try
877
            {
878
                annotation = context.Annotations
879
                   .Where(a => a.Id == annotationId)
880
                   .Include(a => a.User)
881
                   .Include(a => a.Document).ThenInclude(d => d.Content)
882
                   .First();
883
884
            }
885
            catch (Exception ex)
886
            {
887
                throw new InvalidOperationException("Could not find annotation");
888
            }
889
890
891
            if (userRole < ERole.ADMINISTRATOR)
892
            {
893
                if (annotation.User.Id != userId)
894
                {
895
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
896
                }
897
            }
898
899
900
            var tagInstances = context.AnnotationTags.Where(at => at.Instance == instanceId).ToList();
901
            if (tagInstances.Count() == 0)
902
            {
903
                throw new InvalidOperationException("No such instance found");
904
            }
905
906
            foreach (var tagInstance in tagInstances)
907
            {
908
                tagInstance.Sentiment = sentiment;
909
            }
910
911
            context.SaveChanges();
912
        }
913 0ea30313 Vojtěch Bartička
914
        public void MarkAnnotationAsDone(Guid annotationId, Guid userId, ERole userRole, bool done)
915
        {
916
            Annotation annotation = null;
917
            try
918
            {
919
                annotation = context.Annotations
920
                   .Where(a => a.Id == annotationId)
921
                   .Include(a => a.User)
922
                   .Include(a => a.Document).ThenInclude(d => d.Content)
923
                   .First();
924
925
            }
926
            catch (Exception ex)
927
            {
928
                throw new InvalidOperationException("Could not find annotation");
929
            }
930
931
932
            if (userRole < ERole.ADMINISTRATOR)
933
            {
934
                if (annotation.User.Id != userId)
935
                {
936
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
937
                }
938
            }
939
940
            annotation.State = done ? EState.DONE : EState.IN_PROGRESS;
941
            context.SaveChanges();
942
        }
943 eff8ec56 Vojtěch Bartička
    }
944
}