Projekt

Obecné

Profil

Stáhnout (38.4 KB) Statistiky
| Větev: | Tag: | Revize:
1
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
using Microsoft.EntityFrameworkCore;
12
using AutoMapper;
13
using Models.Tags;
14
using Ganss.XSS;
15
using HtmlAgilityPack;
16
using System.Text.RegularExpressions;
17
using Newtonsoft.Json;
18
using Core.GraphUtils;
19

    
20
namespace Core.Services.AnnotationService
21
{
22
    public class AnnotationServiceEF : IAnnotationService
23
    {
24
        private readonly DatabaseContext context;
25
        private readonly ILogger logger;
26
        private readonly IMapper mapper;
27

    
28
        private const string TAG_ID_ATTRIBUTE_NAME = "aswi-tag-id";
29
        private const string TAG_INSTANCE_ATTRIBUTE_NAME = "aswi-tag-instance";
30
        private const string TAG_EF_ID_ATTRIBUTE_NAME = "aswi-tag-ef-id";
31

    
32
        public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper)
33
        {
34
            this.context = context;
35
            this.logger = logger;
36
            this.mapper = mapper;
37
        }
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
            var documents = context.Documents.Where(d => request.DocumentIdList.Contains(d.Id)).ToList();
45
            if (documents.Count() != request.DocumentIdList.Count)
46
            {
47
                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
            }
50

    
51
            var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList();
52
            foreach (var user in users)
53
            {
54
                var userAnnotatedDocuments = context.Annotations.Where(a => a.User == user).Select(a => a.Document).ToList();
55
                foreach (var doc in documents)
56
                {
57
                    if (userAnnotatedDocuments.Contains(doc))
58
                    {
59
                        logger.Information($"User {user.Username} has already been assigned the document {doc.Id}, ignoring");
60
                        continue;
61
                    }
62

    
63
                    context.Annotations.Add(new Annotation()
64
                    {
65
                        User = user,
66
                        UserAssigned = addingUser,
67
                        DateAssigned = DateTime.Now,
68
                        DateLastChanged = DateTime.Now,
69
                        Document = doc,
70
                        State = EState.NEW,
71
                        Note = ""
72
                    });
73
                }
74
            }
75

    
76
            context.SaveChanges();
77
        }
78

    
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

    
103
        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
        public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole)
126
        {
127
            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

    
133
            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
            var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First();
142

    
143
            var tags = context.AnnotationTags.Where(at => at.Annotation.Id == annotationId)
144
                .Include(at => at.Tag)
145
                .ThenInclude(t => t.Category)
146
                .Include(at => at.SubTag)
147
                .OrderBy(at => at.Position)
148
                .ToList();
149

    
150
            List<TagInstanceInfo> tagInstanceInfos = new();
151
            foreach (var tag in tags)
152
            {
153
                var tagInstance = mapper.Map<TagInstanceInfo>(tag);
154
                tagInstanceInfos.Add(tagInstance);
155
            }
156

    
157
            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.CachedCSS = JsonConvert.SerializeObject(TagInstanceCSS);
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

    
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
            NodeDict.Clear();
216

    
217
            return annotationInfo;
218
        }
219

    
220
        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
        private List<TagInstanceCSSInfo> TagInstanceCSS = new();
226

    
227
        /*
228
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
229
         */
230

    
231

    
232
        private string FullPreprocessHTML(string htmlSource, List<AnnotationTag> tags)
233
        {
234
            var docOriginal = new HtmlDocument();
235
            docOriginal.LoadHtml(htmlSource);
236
            var docToEdit = new HtmlDocument();
237
            docToEdit.LoadHtml(htmlSource);
238

    
239
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
240
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
241

    
242
            int currentId = 0;
243

    
244
            FillNodeDict(descendantsOriginal, descendantsToEdit);
245
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
246

    
247
            WrapTextInSpan(descendantsOriginal, docToEdit);
248

    
249
            int descCount = descendantsToEdit.Count;
250

    
251
            foreach (var tag in tags)
252
            {
253
                int i = 0;
254
                List<HtmlNode> addedForSelection = new();
255
                while (i < descCount)
256
                {
257
                    for (; i < descCount; i++)
258
                    {
259
                        var node = descendantsToEdit.ElementAt(i);
260
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
261
                            node.ParentNode.Name == "style")
262
                        {
263
                            continue;
264
                        }
265

    
266
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
267

    
268
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
269
                        var end = TagClosingPositions[nodeId];
270

    
271
                        int selectionStart = tag.Position;
272
                        int selectionEnd = tag.Position + tag.Length;
273

    
274
                        if (selectionStart < end && selectionEnd > start)
275
                        {
276
                            if (selectionStart <= start && selectionEnd >= end)
277
                            {
278
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
279
                            }
280
                            else if (selectionStart <= start)
281
                            {
282
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
283
                            }
284
                            else if (selectionEnd >= end)
285
                            {
286
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
287
                            }
288
                            else
289
                            {
290
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
291
                            }
292
                            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
293
                            descCount = descendantsToEdit.Count;
294
                            break;
295
                        }
296
                    }
297
                }
298

    
299
            }
300

    
301
            string docToRender = docToEdit.DocumentNode.OuterHtml;
302
            HtmlSanitizer sanitizer = new HtmlSanitizer();
303
            sanitizer.AllowedAttributes.Clear();
304
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
305
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
306
            sanitizer.AllowedAttributes.Add("end");
307
            sanitizer.AllowedAttributes.Add("start");
308
            sanitizer.AllowedAttributes.Add("class");
309
            if (sanitizer.AllowedTags.Contains("script"))
310
            {
311
                sanitizer.AllowedTags.Remove("script");
312
            }
313
            if (!sanitizer.AllowedTags.Contains("style"))
314
            {
315
                sanitizer.AllowedTags.Add("style");
316
            }
317
            docToRender = sanitizer.Sanitize(docToRender);        
318
            GenerateCSS(tags);
319
            return docToRender;
320
        }
321

    
322
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
323
        {
324
            // full fill
325
            string textSelected = node.InnerText;
326

    
327
            var parentNode = node.ParentNode;
328
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
329
            parentNode.ChildNodes.RemoveAt(nodeIndex);
330

    
331
            EPosition markerPosition = EPosition.MARK_NONE;
332
            if (selectionEnd == end && selectionStart == start)
333
            {
334
                markerPosition = EPosition.MARK_LEFT_RIGHT;
335
            }
336

    
337
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
338
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
339

    
340
            return spanSelected;
341
        }
342

    
343
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
344
                                             AnnotationTag tag)
345
        {
346
            // partial fill, end gap
347
            string text = node.InnerText;
348
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
349
            string textSelected = text.Substring(0, selectionEnd - start);
350

    
351
            var parentNode = node.ParentNode;
352
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
353
            parentNode.ChildNodes.RemoveAt(nodeIndex);
354

    
355
            int spanSelectedStart = start;
356
            int spanAfterStart = start + textSelected.Length;
357

    
358
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
359
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
360

    
361
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
362
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
363

    
364
            return new() { spanSelected, spanAfter };
365
        }
366

    
367
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
368
                                             AnnotationTag tag)
369
        {
370
            // partial fill, start gap
371
            string text = node.InnerText;
372
            string textBefore = text.Substring(0, selectionStart - start);
373
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
374

    
375
            var parentNode = node.ParentNode;
376
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
377
            parentNode.ChildNodes.RemoveAt(nodeIndex);
378

    
379
            int spanBeforeStart = start;
380
            int spanSelectedStart = start + textBefore.Length;
381

    
382
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
383
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
384

    
385
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT);
386
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
387

    
388
            return new() { spanSelected, spanBefore };
389
        }
390

    
391
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
392
                                                 AnnotationTag tag)
393
        {
394
            // partial fill, start gap end gap
395
            string text = node.InnerText;
396
            string textBefore = text.Substring(0, selectionStart - start);
397
            string textAfter = text.Substring(selectionStart - start + tag.Length);
398
            string textSelected = text.Substring(selectionStart - start, tag.Length);
399

    
400
            var parentNode = node.ParentNode;
401
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
402
            parentNode.ChildNodes.RemoveAt(nodeIndex);
403

    
404
            int spanBeforeStart = start;
405
            int spanSelectedStart = start + textBefore.Length;
406
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
407

    
408
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
409
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
410

    
411
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_LEFT_RIGHT);
412
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
413

    
414
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
415
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
416

    
417
            return new() { spanSelected, spanBefore, spanAfter };
418
        }
419

    
420
        private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE)
421
        {
422
            HtmlNode span = doc.CreateElement("span");
423
            span.InnerHtml = text;
424
            TagStartPositions.Add(startPosition);
425
            TagStartLengths.Add(0);
426
            TagClosingPositions.Add(startPosition + text.Length);
427
            TagClosingLengths.Add(0);
428
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
429

    
430
            if (instanceId != null)
431
            {
432
                span.AddClass("annotation");
433
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
434
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
435

    
436
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
437
                {
438
                    span.Attributes.Add("start", "1");
439
                }
440
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
441
                {
442
                    span.Attributes.Add("end", "1");
443
                }
444
            }
445

    
446
            return span;
447
        }
448

    
449
        private enum EPosition
450
        {
451
            MARK_LEFT = 5,
452
            MARK_RIGHT = 3,
453
            MARK_LEFT_RIGHT = 2,
454
            MARK_NONE = 0
455
        }
456

    
457
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
458
        {
459
            // Special case for non-html documents
460
            if (descendantsOriginal.Count() == 2)
461
            {
462
                var documentNode = descendantsOriginal.ElementAt(0);
463
                var childNode = descendantsOriginal.ElementAt(1);
464
                if (documentNode.Name == "#document" && childNode.Name == "#text")
465
                {
466
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
467
                    coveringSpan.InnerHtml = childNode.InnerHtml;
468
                    TagStartPositions.Add(childNode.InnerStartIndex);
469
                    TagStartLengths.Add(0);
470
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
471
                    TagClosingLengths.Add(0);
472
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
473

    
474
                    var parent = NodeDict[documentNode];
475

    
476
                    parent.ChildNodes.RemoveAt(0);
477
                    parent.ChildNodes.Add(coveringSpan);
478

    
479
                    return;
480
                }
481
            }
482

    
483
            foreach (var node in descendantsOriginal)
484
            {
485
                var originalNode = node;
486
                var toEditNode = NodeDict[node];
487

    
488
                if (originalNode.Name.Contains("#"))
489
                {
490
                    continue;
491
                }
492
                else
493
                {
494
                    bool onlyText = true;
495
                    bool onlySubtags = true;
496

    
497
                    foreach (var child in node.ChildNodes)
498
                    {
499
                        if (child.Name.Contains("#"))
500
                        {
501
                            onlySubtags = false;
502
                        }
503
                        else
504
                        {
505
                            onlyText = false;
506
                        }
507
                    }
508

    
509
                    if (onlyText || onlySubtags)
510
                    {
511
                        continue;
512
                    }
513
                    else
514
                    {
515

    
516
                        foreach (var child in node.ChildNodes)
517
                        {
518
                            if (child.Name.Contains("#text"))
519
                            {
520
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
521
                                coveringSpan.InnerHtml = child.InnerHtml;
522
                                TagStartPositions.Add(child.InnerStartIndex);
523
                                TagStartLengths.Add(0);
524
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
525
                                TagClosingLengths.Add(0);
526
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
527

    
528
                                var parent = NodeDict[node];
529
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
530

    
531
                                parent.ChildNodes.RemoveAt(index);
532
                                parent.ChildNodes.Insert(index, coveringSpan);
533
                            }
534
                        }
535
                    }
536
                }
537
            }
538
        }
539

    
540
        private void GenerateCSS(List<AnnotationTag> tags)
541
        {
542
            /*string inner = "span.annotation {border-bottom: 2px solid;}";
543
            inner += "span {line-height: 30px}\n";*/
544

    
545
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
546
            foreach (var tag in tags)
547
            {
548
                var padding = (tagPaddingDict[tag] + 1) * 2;
549
                TagInstanceCSS.Add(new()
550
                {
551
                    InstanceId = tag.Instance,
552
                    Color = tag.Tag.Color,
553
                    Padding = padding
554
                });             
555
            }
556
        }
557

    
558
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
559
        {
560
            foreach (var node in descendantsOriginal)
561
            {
562
                var originalNode = node;
563
                var toEditNode = NodeDict[node];
564

    
565
                if (originalNode.Name.Contains("#"))
566
                {
567
                    continue;
568
                }
569
                else
570
                {
571
                    TagStartPositions.Add(originalNode.OuterStartIndex);
572
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
573
                    currentId = TagStartPositions.Count - 1;
574
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
575

    
576
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
577
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
578
                }
579
            }
580
        }
581

    
582
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
583
        {
584
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
585
            {
586
                Original = orig,
587
                ToEdit = toEdit
588
            });
589
            foreach (var node in zipped)
590
            {
591
                var originalNode = node.Original;
592
                var toEditNode = node.ToEdit;
593
                NodeDict.Add(originalNode, toEditNode);
594
            }
595
        }
596

    
597

    
598
        /*
599
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
600
         */
601

    
602
        /*
603
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
604
         */
605

    
606
        private string PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTag> tags)
607
        {
608
            var docOriginal = new HtmlDocument();
609
            docOriginal.LoadHtml(htmlOriginal);
610
            var docToEdit = new HtmlDocument();
611
            docToEdit.LoadHtml(htmlToEdit);
612

    
613
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
614

    
615
            int i = 0;
616
            List<HtmlNode> addedForSelection = new();
617

    
618
            int descendantsCount = descendantsToEdit.Count();
619
            while (i < descendantsCount)
620
            {
621
                for (; i < descendantsCount; i++)
622
                {
623
                    var node = descendantsToEdit.ElementAt(i);
624
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
625
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
626
                    {
627
                        continue;
628
                    }
629

    
630
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
631

    
632
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
633
                    var end = TagClosingPositions[nodeId];
634

    
635
                    int selectionStart = tagToAdd.Position;
636
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
637

    
638
                    if (selectionStart < end && selectionEnd > start)
639
                    {
640
                        if (selectionStart <= start && selectionEnd >= end)
641
                        {
642
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
643
                        }
644
                        else if (selectionStart <= start)
645
                        {
646
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
647
                        }
648
                        else if (selectionEnd >= end)
649
                        {
650
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
651
                        }
652
                        else
653
                        {
654
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
655
                        }
656
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
657
                        descendantsCount = descendantsToEdit.Count();
658
                        break;
659
                    }
660
                }
661
            }
662

    
663
            GenerateCSS(tags);
664
            return docToEdit.DocumentNode.OuterHtml;
665
        }
666

    
667

    
668
        private string PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTag> tags)
669
        {
670
            var docOriginal = new HtmlDocument();
671
            docOriginal.LoadHtml(htmlOriginal);
672
            var docToEdit = new HtmlDocument();
673
            docToEdit.LoadHtml(htmlToEdit);
674

    
675
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
676

    
677
            int i = 0;
678
            int descendantsCount = descendantsToEdit.Count();
679
            while (i < descendantsCount)
680
            {
681
                for (; i < descendantsCount; i++)
682
                {
683
                    var node = descendantsToEdit.ElementAt(i);
684
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
685
                    {
686
                        continue;
687
                    }
688
                    else
689
                    {
690
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
691
                        {
692
                            continue;
693
                        }
694

    
695
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
696
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
697
                        node.Attributes.Remove("class");
698
                        node.Attributes.Remove("start");
699
                        node.Attributes.Remove("end");
700

    
701
                        /*var parent = node.ParentNode;
702
                        var contents = node.ChildNodes;
703
                        int index = parent.ChildNodes.IndexOf(node);
704
                        parent.ChildNodes.RemoveAt(index);
705

    
706
                        List<HtmlNode> newChildren = new();
707
                        for (int j = 0; j < index; j++)
708
                        {
709
                            newChildren.Add(parent.ChildNodes[j]);
710
                        }
711
                        for (int j = 0; j < contents.Count; j++)
712
                        {
713
                            newChildren.Add(contents[j]);
714
                        }
715
                        for (int j = index; j < parent.ChildNodes.Count; j++)
716
                        {
717
                            newChildren.Add(parent.ChildNodes[j]);
718
                        }
719

    
720
                        parent.ChildNodes.Clear();
721
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
722

    
723
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
724
                        descendantsCount = descendantsToEdit.Count();
725
                        break;
726
                    }
727
                }
728
            }
729

    
730
            GenerateCSS(tags);
731
            return docToEdit.DocumentNode.OuterHtml;
732
        }
733

    
734
        /*
735
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
736
         */
737

    
738

    
739
        // TODO temporary
740
        private bool IsHtml(string text)
741
        {
742
            return text.Contains("<html>");
743
        }
744

    
745
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
746
        {
747
            var annotation = context.Annotations
748
               .Where(a => a.Id == annotationId)
749
               .Include(a => a.User)
750
               .Include(a => a.Document).ThenInclude(d => d.Content)
751
               .First();
752

    
753
            if (userRole < ERole.ADMINISTRATOR)
754
            {
755
                if (annotation.User.Id != userId)
756
                {
757
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
758
                }
759
            }
760

    
761
            if (annotation.State == EState.NEW)
762
            {
763
                annotation.State = EState.IN_PROGRESS;
764
            }
765

    
766
            AnnotationTag annotationTag = new()
767
            {
768
                Id = Guid.NewGuid(),
769
                Annotation = annotation,
770
                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
771
                Length = request.Length,
772
                Position = request.Position,
773
                Note = ""
774
            };
775

    
776
            if (request.Type == ETagType.TAG)
777
            {
778
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
779
                annotationTag.SubTag = null;
780

    
781
                if (annotationTag.Tag.SentimentEnabled)
782
                {
783
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
784
                }
785

    
786
                // If for the same annotation exists a tag with same position and length and of the same type, ignore
787
                if (context.AnnotationTags.Any(at =>
788
                at.Position == annotationTag.Position &&
789
                at.Length == annotationTag.Length &&
790
                at.Annotation == annotation &&
791
                at.Tag == annotationTag.Tag))
792
                {
793
                    throw new InvalidOperationException("Duplicate tag");
794
                }
795

    
796
            }
797
            else if (request.Type == ETagType.SUBTAG)
798
            {
799
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
800
                annotationTag.SubTag = subTag;
801
                annotationTag.Tag = subTag.Tag;
802

    
803
                if (annotationTag.SubTag.SentimentEnabled)
804
                {
805
                    annotationTag.Sentiment = ETagSentiment.NEUTRAL;
806
                }
807

    
808
                if (context.AnnotationTags.Any(at =>
809
                at.Position == annotationTag.Position &&
810
                at.Length == annotationTag.Length &&
811
                at.Annotation == annotation &&
812
                at.Tag == annotationTag.Tag &&
813
                at.SubTag == annotationTag.SubTag))
814
                {
815
                    throw new InvalidOperationException("Duplicate tag");
816
                }
817
            }
818
            else
819
            {
820
                throw new ArgumentException($"Unknown tag type {request.Type}");
821
            }
822

    
823
            annotation.LastModifiedTagId = annotationTag.Id;
824
            annotation.ModifiedType = EModified.ADDED;
825

    
826
            context.AnnotationTags.Add(annotationTag);
827
            context.SaveChanges();
828
        }
829

    
830
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
831
        {
832
            Annotation annotation = null;
833
            try
834
            {
835
                annotation = context.Annotations
836
                   .Where(a => a.Id == annotationId)
837
                   .Include(a => a.User)
838
                   .Include(a => a.Document).ThenInclude(d => d.Content)
839
                   .First();
840

    
841
            }
842
            catch (Exception ex)
843
            {
844
                throw new InvalidOperationException("Could not find annotation");
845
            }
846

    
847

    
848
            if (userRole < ERole.ADMINISTRATOR)
849
            {
850
                if (annotation.User.Id != loggedUserId)
851
                {
852
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
853
                }
854
            }
855

    
856
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
857
            {
858
                throw new InvalidOperationException("Could not find tag instance");
859
            }
860

    
861

    
862
            var annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
863
            annotation.LastModifiedTagId = annotationTag.Id;
864
            annotation.ModifiedType = EModified.REMOVED;
865

    
866
            context.AnnotationTags.Remove(annotationTag);
867

    
868
            context.SaveChanges();
869
        }
870

    
871
        public void SetTagInstanceSentiment(Guid annotationId, Guid instanceId, Guid userId, ERole userRole, ETagSentiment sentiment)
872
        {
873
            Annotation annotation = null;
874
            try
875
            {
876
                annotation = context.Annotations
877
                   .Where(a => a.Id == annotationId)
878
                   .Include(a => a.User)
879
                   .Include(a => a.Document).ThenInclude(d => d.Content)
880
                   .First();
881

    
882
            }
883
            catch (Exception ex)
884
            {
885
                throw new InvalidOperationException("Could not find annotation");
886
            }
887

    
888

    
889
            if (userRole < ERole.ADMINISTRATOR)
890
            {
891
                if (annotation.User.Id != userId)
892
                {
893
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
894
                }
895
            }
896

    
897

    
898
            var tagInstances = context.AnnotationTags.Where(at => at.Instance == instanceId).ToList();
899
            if (tagInstances.Count() == 0)
900
            {
901
                throw new InvalidOperationException("No such instance found");
902
            }
903

    
904
            foreach (var tagInstance in tagInstances)
905
            {
906
                tagInstance.Sentiment = sentiment;
907
            }
908

    
909
            context.SaveChanges();
910
        }
911

    
912
        public void MarkAnnotationAsDone(Guid annotationId, Guid userId, ERole userRole, bool done)
913
        {
914
            Annotation annotation = null;
915
            try
916
            {
917
                annotation = context.Annotations
918
                   .Where(a => a.Id == annotationId)
919
                   .Include(a => a.User)
920
                   .Include(a => a.Document).ThenInclude(d => d.Content)
921
                   .First();
922

    
923
            }
924
            catch (Exception ex)
925
            {
926
                throw new InvalidOperationException("Could not find annotation");
927
            }
928

    
929

    
930
            if (userRole < ERole.ADMINISTRATOR)
931
            {
932
                if (annotation.User.Id != userId)
933
                {
934
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
935
                }
936
            }
937

    
938
            annotation.State = done ? EState.DONE : EState.IN_PROGRESS;
939
            context.SaveChanges();
940
        }
941
    }
942
}
(1-1/2)