Projekt

Obecné

Profil

Stáhnout (38.2 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
                //annotation.CachedNodeDict = JsonConvert.SerializeObject(NodeDict);
166
                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
178
                // The annotation has been modified and we need to either add the new tag or remove the tag
179
                if (annotation.ModifiedType != EModified.NONE)
180
                {
181
                    if (annotation.ModifiedType == EModified.ADDED)
182
                    {
183
                        var lastModifiedTag = context.AnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First();
184
                        docToRender = PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags);
185
                    }
186
                    else if (annotation.ModifiedType == EModified.REMOVED)
187
                    {
188
                        docToRender = PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags);
189
                    }
190
191
                    annotation.ModifiedType = EModified.NONE;
192
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
193
                    annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
194
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
195
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
196
                    annotation.CachedDocumentHTML = docToRender;
197
                    //annotation.CachedNodeDict = JsonConvert.SerializeObject(NodeDict);
198
                    context.SaveChanges();
199
                }
200
            }
201 0a9f9349 Vojtěch Bartička
202
            // We probably cannot use AutoMapper since we are dealing with too many different entities
203
            AnnotationInfo annotationInfo = new()
204
            {
205
                SourceDocumentContent = documentContent.Content,
206
                DocumentToRender = docToRender,
207
                TagStartPositions = TagStartPositions.ToArray(),
208
                TagLengths = TagStartLengths.ToArray(),
209
                Note = annotation.Note,
210
                State = annotation.State,
211
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
212
                TagInstances = tagInstanceInfos
213
            };
214
215 553486ad Vojtěch Bartička
            NodeDict.Clear();
216
217 a6675a6d Vojtěch Bartička
            return annotationInfo;
218
        }
219
220 0a9f9349 Vojtěch Bartička
        private List<int> TagStartPositions = new();
221
        private List<int> TagStartLengths = new();
222
        private List<int> TagClosingPositions = new();
223
        private List<int> TagClosingLengths = new();
224
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
225
226 553486ad Vojtěch Bartička
        /*
227
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
228
         */
229
230
231
        private string FullPreprocessHTML(string htmlSource, List<AnnotationTag> tags)
232 3c185841 Vojtěch Bartička
        {
233
            var docOriginal = new HtmlDocument();
234
            docOriginal.LoadHtml(htmlSource);
235
            var docToEdit = new HtmlDocument();
236
            docToEdit.LoadHtml(htmlSource);
237
238
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
239 553486ad Vojtěch Bartička
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
240 3c185841 Vojtěch Bartička
241 0a9f9349 Vojtěch Bartička
            int currentId = 0;
242 3c185841 Vojtěch Bartička
243 0a9f9349 Vojtěch Bartička
            FillNodeDict(descendantsOriginal, descendantsToEdit);
244
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
245 3c185841 Vojtěch Bartička
246 0a9f9349 Vojtěch Bartička
            WrapTextInSpan(descendantsOriginal, docToEdit);
247 3c185841 Vojtěch Bartička
248 553486ad Vojtěch Bartička
            int descCount = descendantsToEdit.Count;
249
250 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
251 3c185841 Vojtěch Bartička
            {
252 0a9f9349 Vojtěch Bartička
                int i = 0;
253
                List<HtmlNode> addedForSelection = new();
254 553486ad Vojtěch Bartička
                while (i < descCount)
255 3c185841 Vojtěch Bartička
                {
256 553486ad Vojtěch Bartička
                    for (; i < descCount; i++)
257 0a9f9349 Vojtěch Bartička
                    {
258
                        var node = descendantsToEdit.ElementAt(i);
259
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
260
                            node.ParentNode.Name == "style")
261
                        {
262
                            continue;
263
                        }
264
265 c9762683 Vojtěch Bartička
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
266
267
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
268
                        var end = TagClosingPositions[nodeId];
269 0a9f9349 Vojtěch Bartička
270
                        int selectionStart = tag.Position;
271
                        int selectionEnd = tag.Position + tag.Length;
272
273
                        if (selectionStart < end && selectionEnd > start)
274
                        {
275
                            if (selectionStart <= start && selectionEnd >= end)
276
                            {
277
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
278
                            }
279
                            else if (selectionStart <= start)
280
                            {
281
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
282
                            }
283
                            else if (selectionEnd >= end)
284
                            {
285
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
286
                            }
287
                            else
288
                            {
289
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
290
                            }
291 553486ad Vojtěch Bartička
                            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
292
                            descCount = descendantsToEdit.Count;
293 0a9f9349 Vojtěch Bartička
                            break;
294
                        }
295
                    }
296 3c185841 Vojtěch Bartička
                }
297
298
            }
299
300
            string docToRender = docToEdit.DocumentNode.OuterHtml;
301
            HtmlSanitizer sanitizer = new HtmlSanitizer();
302
            sanitizer.AllowedAttributes.Clear();
303 0a9f9349 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
304
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
305 c9762683 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add("end");
306
            sanitizer.AllowedAttributes.Add("start");
307
            sanitizer.AllowedAttributes.Add("class");
308 be4deff8 Vojtěch Bartička
            if (sanitizer.AllowedTags.Contains("script"))
309
            {
310
                sanitizer.AllowedTags.Remove("script");
311 553486ad Vojtěch Bartička
            }
312 c9762683 Vojtěch Bartička
            if (!sanitizer.AllowedTags.Contains("style"))
313
            {
314
                sanitizer.AllowedTags.Add("style");
315 be4deff8 Vojtěch Bartička
            }
316 3c185841 Vojtěch Bartička
            docToRender = sanitizer.Sanitize(docToRender);
317
318 c9762683 Vojtěch Bartička
            HtmlDocument doc = new HtmlDocument();
319
            doc.LoadHtml(docToRender);
320 8dc25caa Vojtěch Bartička
            var cssNode = GenerateCSS(doc, tags);
321 c9762683 Vojtěch Bartička
            doc.DocumentNode.ChildNodes.Insert(0, cssNode);
322
323
            return doc.DocumentNode.OuterHtml;
324 0a9f9349 Vojtěch Bartička
        }
325
326
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
327
        {
328
            // full fill
329
            string textSelected = node.InnerText;
330
331
            var parentNode = node.ParentNode;
332
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
333
            parentNode.ChildNodes.RemoveAt(nodeIndex);
334
335
            EPosition markerPosition = EPosition.MARK_NONE;
336
            if (selectionEnd == end && selectionStart == start)
337
            {
338
                markerPosition = EPosition.MARK_LEFT_RIGHT;
339
            }
340
341 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
342 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
343
344
            return spanSelected;
345
        }
346
347
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
348
                                             AnnotationTag tag)
349
        {
350
            // partial fill, end gap
351
            string text = node.InnerText;
352
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
353
            string textSelected = text.Substring(0, selectionEnd - start);
354
355
            var parentNode = node.ParentNode;
356
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
357
            parentNode.ChildNodes.RemoveAt(nodeIndex);
358
359
            int spanSelectedStart = start;
360
            int spanAfterStart = start + textSelected.Length;
361
362 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
363 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
364
365 553486ad Vojtěch Bartička
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
366 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
367
368
            return new() { spanSelected, spanAfter };
369
        }
370
371
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
372
                                             AnnotationTag tag)
373
        {
374
            // partial fill, start gap
375
            string text = node.InnerText;
376
            string textBefore = text.Substring(0, selectionStart - start);
377
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
378
379
            var parentNode = node.ParentNode;
380
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
381
            parentNode.ChildNodes.RemoveAt(nodeIndex);
382
383
            int spanBeforeStart = start;
384
            int spanSelectedStart = start + textBefore.Length;
385
386 553486ad Vojtěch Bartička
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
387 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
388
389 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT);
390 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
391
392
            return new() { spanSelected, spanBefore };
393
        }
394
395
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
396
                                                 AnnotationTag tag)
397
        {
398
            // partial fill, start gap end gap
399
            string text = node.InnerText;
400
            string textBefore = text.Substring(0, selectionStart - start);
401
            string textAfter = text.Substring(selectionStart - start + tag.Length);
402
            string textSelected = text.Substring(selectionStart - start, tag.Length);
403
404
            var parentNode = node.ParentNode;
405
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
406
            parentNode.ChildNodes.RemoveAt(nodeIndex);
407
408
            int spanBeforeStart = start;
409
            int spanSelectedStart = start + textBefore.Length;
410
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
411
412 553486ad Vojtěch Bartička
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
413 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
414
415 553486ad Vojtěch Bartička
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT_RIGHT);
416 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
417
418 553486ad Vojtěch Bartička
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
419 0a9f9349 Vojtěch Bartička
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
420
421
            return new() { spanSelected, spanBefore, spanAfter };
422
        }
423
424 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)
425 0a9f9349 Vojtěch Bartička
        {
426
            HtmlNode span = doc.CreateElement("span");
427
            span.InnerHtml = text;
428
            TagStartPositions.Add(startPosition);
429
            TagStartLengths.Add(0);
430
            TagClosingPositions.Add(startPosition + text.Length);
431
            TagClosingLengths.Add(0);
432 553486ad Vojtěch Bartička
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
433 0a9f9349 Vojtěch Bartička
434
            if (instanceId != null)
435
            {
436
                span.AddClass("annotation");
437 553486ad Vojtěch Bartička
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
438
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
439 0a9f9349 Vojtěch Bartička
440
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
441
                {
442
                    span.Attributes.Add("start", "1");
443
                }
444
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
445
                {
446
                    span.Attributes.Add("end", "1");
447
                }
448
            }
449
450
            return span;
451
        }
452
453
        private enum EPosition
454
        {
455
            MARK_LEFT = 5,
456
            MARK_RIGHT = 3,
457
            MARK_LEFT_RIGHT = 2,
458
            MARK_NONE = 0
459
        }
460
461
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
462
        {
463 ecf6d2e0 Vojtěch Bartička
            // Special case for non-html documents
464
            if (descendantsOriginal.Count() == 2)
465
            {
466
                var documentNode = descendantsOriginal.ElementAt(0);
467
                var childNode = descendantsOriginal.ElementAt(1);
468
                if (documentNode.Name == "#document" && childNode.Name == "#text")
469
                {
470
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
471
                    coveringSpan.InnerHtml = childNode.InnerHtml;
472
                    TagStartPositions.Add(childNode.InnerStartIndex);
473
                    TagStartLengths.Add(0);
474
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
475
                    TagClosingLengths.Add(0);
476
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
477
478
                    var parent = NodeDict[documentNode];
479
480
                    parent.ChildNodes.RemoveAt(0);
481
                    parent.ChildNodes.Add(coveringSpan);
482
483
                    return;
484
                }
485
            }
486
487 0a9f9349 Vojtěch Bartička
            foreach (var node in descendantsOriginal)
488
            {
489
                var originalNode = node;
490
                var toEditNode = NodeDict[node];
491
492
                if (originalNode.Name.Contains("#"))
493
                {
494
                    continue;
495
                }
496
                else
497
                {
498
                    bool onlyText = true;
499
                    bool onlySubtags = true;
500
501
                    foreach (var child in node.ChildNodes)
502
                    {
503
                        if (child.Name.Contains("#"))
504
                        {
505
                            onlySubtags = false;
506
                        }
507
                        else
508
                        {
509
                            onlyText = false;
510
                        }
511
                    }
512
513
                    if (onlyText || onlySubtags)
514
                    {
515
                        continue;
516
                    }
517
                    else
518
                    {
519
520
                        foreach (var child in node.ChildNodes)
521
                        {
522
                            if (child.Name.Contains("#text"))
523
                            {
524
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
525
                                coveringSpan.InnerHtml = child.InnerHtml;
526
                                TagStartPositions.Add(child.InnerStartIndex);
527
                                TagStartLengths.Add(0);
528
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
529
                                TagClosingLengths.Add(0);
530
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
531
532
                                var parent = NodeDict[node];
533
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
534
535
                                parent.ChildNodes.RemoveAt(index);
536
                                parent.ChildNodes.Insert(index, coveringSpan);
537
                            }
538
                        }
539
                    }
540
                }
541
            }
542
        }
543
544 8dc25caa Vojtěch Bartička
        private HtmlNode GenerateCSS(HtmlDocument docToEdit, List<AnnotationTag> tags)
545 0a9f9349 Vojtěch Bartička
        {
546
            HtmlNode style = docToEdit.CreateElement("style");
547
548
            string inner = "span.annotation {border-bottom: 2px solid;}";
549
            inner += "span {line-height: 30px}\n";
550
551 8dc25caa Vojtěch Bartička
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
552
553 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
554
            {
555 8dc25caa Vojtěch Bartička
                var padding = (tagPaddingDict[tag] + 1) * 2;
556
                inner += $"span[{TAG_INSTANCE_ATTRIBUTE_NAME}=\"{tag.Instance}\"] {{ border-color:{tag.Tag.Color}; padding-bottom: {padding}px }}";
557 0a9f9349 Vojtěch Bartička
            }
558
559
            inner += "span[end=\"1\"] {border-end-end-radius: 0px; border-right: 1px solid darkgray}\n";
560
            inner += "span[start=\"1\"] {border-end-start-radius: 0px; border-left: 1px solid darkgray}\n";
561
562
            style.InnerHtml = inner;
563
            return style;
564
        }
565
566
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
567
        {
568
            foreach (var node in descendantsOriginal)
569
            {
570
                var originalNode = node;
571
                var toEditNode = NodeDict[node];
572
573
                if (originalNode.Name.Contains("#"))
574
                {
575
                    continue;
576
                }
577
                else
578
                {
579
                    TagStartPositions.Add(originalNode.OuterStartIndex);
580
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
581
                    currentId = TagStartPositions.Count - 1;
582
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
583
584
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
585
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
586
                }
587
            }
588
        }
589
590
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
591
        {
592
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
593
            {
594
                Original = orig,
595
                ToEdit = toEdit
596
            });
597
            foreach (var node in zipped)
598
            {
599
                var originalNode = node.Original;
600
                var toEditNode = node.ToEdit;
601
                NodeDict.Add(originalNode, toEditNode);
602
            }
603 3c185841 Vojtěch Bartička
        }
604
605 553486ad Vojtěch Bartička
606
        /*
607
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
608
         */
609
610
        /*
611
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
612
         */
613
614
        private string PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTag> tags)
615
        {
616
            var docOriginal = new HtmlDocument();
617
            docOriginal.LoadHtml(htmlOriginal);
618
            var docToEdit = new HtmlDocument();
619
            docToEdit.LoadHtml(htmlToEdit);
620
621
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
622
            RemoveCSS(docToEdit);
623
624
            int i = 0;
625
            List<HtmlNode> addedForSelection = new();
626
627
            int descendantsCount = descendantsToEdit.Count();
628
            while (i < descendantsCount)
629
            {
630
                for (; i < descendantsCount; i++)
631
                {
632
                    var node = descendantsToEdit.ElementAt(i);
633
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
634 ecf6d2e0 Vojtěch Bartička
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
635 553486ad Vojtěch Bartička
                    {
636
                        continue;
637
                    }
638
639
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
640
641
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
642
                    var end = TagClosingPositions[nodeId];
643
644
                    int selectionStart = tagToAdd.Position;
645
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
646
647
                    if (selectionStart < end && selectionEnd > start)
648
                    {
649
                        if (selectionStart <= start && selectionEnd >= end)
650
                        {
651
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
652
                        }
653
                        else if (selectionStart <= start)
654
                        {
655
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
656
                        }
657
                        else if (selectionEnd >= end)
658
                        {
659
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
660
                        }
661
                        else
662
                        {
663
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
664
                        }
665
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
666
                        descendantsCount = descendantsToEdit.Count();
667
                        break;
668
                    }
669
                }
670
            }
671
672 8dc25caa Vojtěch Bartička
            var cssNode = GenerateCSS(docToEdit, tags);
673 553486ad Vojtěch Bartička
            docToEdit.DocumentNode.ChildNodes.Insert(0, cssNode);
674
675
            return docToEdit.DocumentNode.OuterHtml;
676
        }
677
678
679
        private string PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTag> tags)
680
        {
681
            var docOriginal = new HtmlDocument();
682
            docOriginal.LoadHtml(htmlOriginal);
683
            var docToEdit = new HtmlDocument();
684
            docToEdit.LoadHtml(htmlToEdit);
685
686
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
687
            RemoveCSS(docToEdit);
688
689
            int i = 0;
690
            int descendantsCount = descendantsToEdit.Count();
691
            while (i < descendantsCount)
692
            {
693
                for (; i < descendantsCount; i++)
694
                {
695
                    var node = descendantsToEdit.ElementAt(i);
696
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
697
                    {
698
                        continue;
699
                    }
700
                    else
701
                    {
702
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
703
                        {
704
                            continue;
705
                        }
706
707
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
708
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
709
                        node.Attributes.Remove("class");
710
                        node.Attributes.Remove("start");
711
                        node.Attributes.Remove("end");
712
713
                        /*var parent = node.ParentNode;
714
                        var contents = node.ChildNodes;
715
                        int index = parent.ChildNodes.IndexOf(node);
716
                        parent.ChildNodes.RemoveAt(index);
717
718
                        List<HtmlNode> newChildren = new();
719
                        for (int j = 0; j < index; j++)
720
                        {
721
                            newChildren.Add(parent.ChildNodes[j]);
722
                        }
723
                        for (int j = 0; j < contents.Count; j++)
724
                        {
725
                            newChildren.Add(contents[j]);
726
                        }
727
                        for (int j = index; j < parent.ChildNodes.Count; j++)
728
                        {
729
                            newChildren.Add(parent.ChildNodes[j]);
730
                        }
731
732
                        parent.ChildNodes.Clear();
733
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
734 58363b44 Vojtěch Bartička
735 553486ad Vojtěch Bartička
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
736
                        descendantsCount = descendantsToEdit.Count();
737
                        break;
738
                    }
739
                }
740
            }
741
742 8dc25caa Vojtěch Bartička
            var cssNode = GenerateCSS(docToEdit, tags);
743 553486ad Vojtěch Bartička
            docToEdit.DocumentNode.ChildNodes.Insert(0, cssNode);
744
745
            return docToEdit.DocumentNode.OuterHtml;
746
        }
747
748
        private void RemoveCSS(HtmlDocument doc)
749
        {
750
            doc.DocumentNode.ChildNodes.RemoveAt(0);
751
        }
752
753
        /*
754
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
755
         */
756
757
758 a6675a6d Vojtěch Bartička
        // TODO temporary
759
        private bool IsHtml(string text)
760
        {
761 0a9f9349 Vojtěch Bartička
            return text.Contains("<html>");
762 a6675a6d Vojtěch Bartička
        }
763 be4deff8 Vojtěch Bartička
764
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
765
        {
766
            var annotation = context.Annotations
767
               .Where(a => a.Id == annotationId)
768
               .Include(a => a.User)
769
               .Include(a => a.Document).ThenInclude(d => d.Content)
770
               .First();
771
772
            if (userRole < ERole.ADMINISTRATOR)
773
            {
774
                if (annotation.User.Id != userId)
775
                {
776
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
777
                }
778
            }
779
780 2f3821f0 Vojtěch Bartička
            if (annotation.State == EState.NEW)
781
            {
782
                annotation.State = EState.IN_PROGRESS;
783
            }
784
785 be4deff8 Vojtěch Bartička
            AnnotationTag annotationTag = new()
786
            {
787 553486ad Vojtěch Bartička
                Id = Guid.NewGuid(),
788 be4deff8 Vojtěch Bartička
                Annotation = annotation,
789
                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
790
                Length = request.Length,
791
                Position = request.Position,
792
                Note = ""
793
            };
794
795
            if (request.Type == ETagType.TAG)
796
            {
797
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
798
                annotationTag.SubTag = null;
799 15c88dc1 Vojtěch Bartička
800
                if (annotationTag.Tag.SentimentEnabled)
801
                {
802
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
803
                }
804 2f3821f0 Vojtěch Bartička
805 58363b44 Vojtěch Bartička
                // If for the same annotation exists a tag with same position and length and of the same type, ignore
806 2f3821f0 Vojtěch Bartička
                if (context.AnnotationTags.Any(at =>
807 58363b44 Vojtěch Bartička
                at.Position == annotationTag.Position &&
808
                at.Length == annotationTag.Length &&
809
                at.Annotation == annotation &&
810
                at.Tag == annotationTag.Tag))
811
                {
812
                    throw new InvalidOperationException("Duplicate tag");
813
                }
814
815 be4deff8 Vojtěch Bartička
            }
816
            else if (request.Type == ETagType.SUBTAG)
817
            {
818
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
819
                annotationTag.SubTag = subTag;
820
                annotationTag.Tag = subTag.Tag;
821 15c88dc1 Vojtěch Bartička
822
                if (annotationTag.SubTag.SentimentEnabled)
823
                {
824
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
825
                }
826 2f3821f0 Vojtěch Bartička
827
                if (context.AnnotationTags.Any(at =>
828 58363b44 Vojtěch Bartička
                at.Position == annotationTag.Position &&
829
                at.Length == annotationTag.Length &&
830
                at.Annotation == annotation &&
831
                at.Tag == annotationTag.Tag &&
832
                at.SubTag == annotationTag.SubTag))
833
                {
834
                    throw new InvalidOperationException("Duplicate tag");
835
                }
836 be4deff8 Vojtěch Bartička
            }
837
            else
838
            {
839
                throw new ArgumentException($"Unknown tag type {request.Type}");
840
            }
841
842 553486ad Vojtěch Bartička
            annotation.LastModifiedTagId = annotationTag.Id;
843
            annotation.ModifiedType = EModified.ADDED;
844
845 abf94f21 Lukáš Vlček
            context.AnnotationTags.Add(annotationTag);
846 be4deff8 Vojtěch Bartička
            context.SaveChanges();
847
        }
848 0f8d6304 Vojtěch Bartička
849
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
850
        {
851
            Annotation annotation = null;
852
            try
853
            {
854
                annotation = context.Annotations
855
                   .Where(a => a.Id == annotationId)
856
                   .Include(a => a.User)
857
                   .Include(a => a.Document).ThenInclude(d => d.Content)
858
                   .First();
859
860
            }
861
            catch (Exception ex)
862
            {
863
                throw new InvalidOperationException("Could not find annotation");
864
            }
865
866
867
            if (userRole < ERole.ADMINISTRATOR)
868
            {
869
                if (annotation.User.Id != loggedUserId)
870
                {
871
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
872
                }
873
            }
874
875
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
876
            {
877
                throw new InvalidOperationException("Could not find tag instance");
878
            }
879
880 553486ad Vojtěch Bartička
881
            var annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
882
            annotation.LastModifiedTagId = annotationTag.Id;
883
            annotation.ModifiedType = EModified.REMOVED;
884
885
            context.AnnotationTags.Remove(annotationTag);
886 0a9f9349 Vojtěch Bartička
887 0f8d6304 Vojtěch Bartička
            context.SaveChanges();
888
        }
889 15c88dc1 Vojtěch Bartička
890
        public void SetTagInstanceSentiment(Guid annotationId, Guid instanceId, Guid userId, ERole userRole, ETagSentiment sentiment)
891
        {
892
            Annotation annotation = null;
893
            try
894
            {
895
                annotation = context.Annotations
896
                   .Where(a => a.Id == annotationId)
897
                   .Include(a => a.User)
898
                   .Include(a => a.Document).ThenInclude(d => d.Content)
899
                   .First();
900
901
            }
902
            catch (Exception ex)
903
            {
904
                throw new InvalidOperationException("Could not find annotation");
905
            }
906
907
908
            if (userRole < ERole.ADMINISTRATOR)
909
            {
910
                if (annotation.User.Id != userId)
911
                {
912
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
913
                }
914
            }
915
916
917
            var tagInstances = context.AnnotationTags.Where(at => at.Instance == instanceId).ToList();
918
            if (tagInstances.Count() == 0)
919
            {
920
                throw new InvalidOperationException("No such instance found");
921
            }
922
923
            foreach (var tagInstance in tagInstances)
924
            {
925
                tagInstance.Sentiment = sentiment;
926
            }
927
928
            context.SaveChanges();
929
        }
930 eff8ec56 Vojtěch Bartička
    }
931
}