Projekt

Obecné

Profil

Stáhnout (34.5 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

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

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

    
31
        public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper)
32
        {
33
            this.context = context;
34
            this.logger = logger;
35
            this.mapper = mapper;
36
        }
37

    
38
        public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId)
39
        {
40
            User addingUser = context.Users.Single(u => u.Id == clientUserId);
41

    
42
            // Check the documents exist
43
            var documents = context.Documents.Where(d => request.DocumentIdList.Contains(d.Id)).ToList();
44
            if (documents.Count() != request.DocumentIdList.Count)
45
            {
46
                logger.Information($"Received a non-existent Document ID when assigning documents to users");
47
                throw new InvalidOperationException($"{request.DocumentIdList.Count - documents.Count()} of the received documents do not exist");
48
            }
49

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

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

    
75
            context.SaveChanges();
76
        }
77

    
78
        public AnnotationListResponse GetUserAnnotations(Guid userId)
79
        {
80
            var annotations = context.Annotations.Where(a => a.User.Id == userId).Include(a => a.Document).ToList();
81
            var documentIds = annotations.Select(a => a.Document.Id).ToList();
82
            var documents = context.Documents.Where(d => documentIds.Contains(d.Id));
83
            var infos = new List<AnnotationListInfo>();
84

    
85
            var annotationsDocuments = annotations.Zip(documents, (a, d) => new { Annotation = a, Document = d });
86
            foreach (var ad in annotationsDocuments)
87
            {
88
                infos.Add(new AnnotationListInfo()
89
                {
90
                    AnnotationId = ad.Annotation.Id,
91
                    DocumentName = ad.Document.Name,
92
                    State = ad.Annotation.State
93
                });
94
            }
95

    
96
            return new AnnotationListResponse()
97
            {
98
                Annotations = infos
99
            };
100
        }
101

    
102
        public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole)
103
        {
104
            var annotation = context.Annotations
105
                .Where(a => a.Id == annotationId)
106
                .Include(a => a.User)
107
                .Include(a => a.Document).ThenInclude(d => d.Content)
108
                .First();
109

    
110
            if (userRole < ERole.ADMINISTRATOR)
111
            {
112
                if (annotation.User.Id != userId)
113
                {
114
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
115
                }
116
            }
117

    
118
            var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First();
119

    
120
            var tags = context.AnnotationTags.Where(at => at.Annotation.Id == annotationId)
121
                .Include(at => at.Tag)
122
                .ThenInclude(t => t.Category)
123
                .Include(at => at.SubTag)
124
                .ToList();
125

    
126
            List<TagInstanceInfo> tagInstanceInfos = new();
127
            foreach (var tag in tags)
128
            {
129
                var tagInstance = mapper.Map<TagInstanceInfo>(tag);
130
                tagInstanceInfos.Add(tagInstance);
131
            }
132

    
133
            var docToRender = "";
134
            if (annotation.CachedDocumentHTML == "")
135
            {
136
                docToRender = FullPreprocessHTML(documentContent.Content, tags);
137
                annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
138
                annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
139
                annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
140
                annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
141
                //annotation.CachedNodeDict = JsonConvert.SerializeObject(NodeDict);
142
                annotation.ModifiedType = EModified.NONE;
143
                annotation.CachedDocumentHTML = docToRender;
144
                context.SaveChanges();
145
            }
146
            else
147
            {
148
                docToRender = annotation.CachedDocumentHTML;
149
                TagStartPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedStartPositions);
150
                TagStartLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedLengths);
151
                TagClosingPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingPositions);
152
                TagClosingLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingLengths);
153

    
154
                // The annotation has been modified and we need to either add the new tag or remove the tag
155
                if (annotation.ModifiedType != EModified.NONE)
156
                {
157
                    if (annotation.ModifiedType == EModified.ADDED)
158
                    {
159
                        var lastModifiedTag = context.AnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First();
160
                        docToRender = PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags);
161
                    }
162
                    else if (annotation.ModifiedType == EModified.REMOVED)
163
                    {
164
                        docToRender = PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags);
165
                    }
166

    
167
                    annotation.ModifiedType = EModified.NONE;
168
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
169
                    annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
170
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
171
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
172
                    annotation.CachedDocumentHTML = docToRender;
173
                    //annotation.CachedNodeDict = JsonConvert.SerializeObject(NodeDict);
174
                    context.SaveChanges();
175
                }
176
            }
177

    
178
            // We probably cannot use AutoMapper since we are dealing with too many different entities
179
            AnnotationInfo annotationInfo = new()
180
            {
181
                SourceDocumentContent = documentContent.Content,
182
                DocumentToRender = docToRender,
183
                TagStartPositions = TagStartPositions.ToArray(),
184
                TagLengths = TagStartLengths.ToArray(),
185
                Note = annotation.Note,
186
                State = annotation.State,
187
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
188
                TagInstances = tagInstanceInfos
189
            };
190

    
191
            NodeDict.Clear();
192

    
193
            return annotationInfo;
194
        }
195

    
196
        private List<int> TagStartPositions = new();
197
        private List<int> TagStartLengths = new();
198
        private List<int> TagClosingPositions = new();
199
        private List<int> TagClosingLengths = new();
200
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
201

    
202
        /*
203
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
204
         */
205

    
206

    
207
        private string FullPreprocessHTML(string htmlSource, List<AnnotationTag> tags)
208
        {
209
            var docOriginal = new HtmlDocument();
210
            docOriginal.LoadHtml(htmlSource);
211
            var docToEdit = new HtmlDocument();
212
            docToEdit.LoadHtml(htmlSource);
213

    
214
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
215
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
216

    
217
            int currentId = 0;
218

    
219
            FillNodeDict(descendantsOriginal, descendantsToEdit);
220
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
221

    
222
            WrapTextInSpan(descendantsOriginal, docToEdit);
223

    
224
            int descCount = descendantsToEdit.Count;
225

    
226
            foreach (var tag in tags)
227
            {
228
                int i = 0;
229
                List<HtmlNode> addedForSelection = new();
230
                while (i < descCount)
231
                {
232
                    for (; i < descCount; i++)
233
                    {
234
                        var node = descendantsToEdit.ElementAt(i);
235
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
236
                            node.ParentNode.Name == "style")
237
                        {
238
                            continue;
239
                        }
240

    
241
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
242

    
243
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
244
                        var end = TagClosingPositions[nodeId];
245

    
246
                        int selectionStart = tag.Position;
247
                        int selectionEnd = tag.Position + tag.Length;
248

    
249
                        if (selectionStart < end && selectionEnd > start)
250
                        {
251
                            if (selectionStart <= start && selectionEnd >= end)
252
                            {
253
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
254
                            }
255
                            else if (selectionStart <= start)
256
                            {
257
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
258
                            }
259
                            else if (selectionEnd >= end)
260
                            {
261
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
262
                            }
263
                            else
264
                            {
265
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
266
                            }
267
                            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
268
                            descCount = descendantsToEdit.Count;
269
                            break;
270
                        }
271
                    }
272
                }
273

    
274
            }
275

    
276
            string docToRender = docToEdit.DocumentNode.OuterHtml;
277
            HtmlSanitizer sanitizer = new HtmlSanitizer();
278
            sanitizer.AllowedAttributes.Clear();
279
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
280
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
281
            sanitizer.AllowedAttributes.Add("end");
282
            sanitizer.AllowedAttributes.Add("start");
283
            sanitizer.AllowedAttributes.Add("class");
284
            if (sanitizer.AllowedTags.Contains("script"))
285
            {
286
                sanitizer.AllowedTags.Remove("script");
287
            }
288
            if (!sanitizer.AllowedTags.Contains("style"))
289
            {
290
                sanitizer.AllowedTags.Add("style");
291
            }
292
            docToRender = sanitizer.Sanitize(docToRender);
293

    
294
            HtmlDocument doc = new HtmlDocument();
295
            doc.LoadHtml(docToRender);
296
            var cssNode = GenerateCSS(doc, tags, new());
297
            doc.DocumentNode.ChildNodes.Insert(0, cssNode);
298

    
299
            return doc.DocumentNode.OuterHtml;
300
        }
301

    
302
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
303
        {
304
            // full fill
305
            string textSelected = node.InnerText;
306

    
307
            var parentNode = node.ParentNode;
308
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
309
            parentNode.ChildNodes.RemoveAt(nodeIndex);
310

    
311
            EPosition markerPosition = EPosition.MARK_NONE;
312
            if (selectionEnd == end && selectionStart == start)
313
            {
314
                markerPosition = EPosition.MARK_LEFT_RIGHT;
315
            }
316

    
317
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
318
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
319

    
320
            return spanSelected;
321
        }
322

    
323
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
324
                                             AnnotationTag tag)
325
        {
326
            // partial fill, end gap
327
            string text = node.InnerText;
328
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
329
            string textSelected = text.Substring(0, selectionEnd - start);
330

    
331
            var parentNode = node.ParentNode;
332
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
333
            parentNode.ChildNodes.RemoveAt(nodeIndex);
334

    
335
            int spanSelectedStart = start;
336
            int spanAfterStart = start + textSelected.Length;
337

    
338
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
339
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
340

    
341
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
342
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
343

    
344
            return new() { spanSelected, spanAfter };
345
        }
346

    
347
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
348
                                             AnnotationTag tag)
349
        {
350
            // partial fill, start gap
351
            string text = node.InnerText;
352
            string textBefore = text.Substring(0, selectionStart - start);
353
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
354

    
355
            var parentNode = node.ParentNode;
356
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
357
            parentNode.ChildNodes.RemoveAt(nodeIndex);
358

    
359
            int spanBeforeStart = start;
360
            int spanSelectedStart = start + textBefore.Length;
361

    
362
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
363
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
364

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

    
368
            return new() { spanSelected, spanBefore };
369
        }
370

    
371
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
372
                                                 AnnotationTag tag)
373
        {
374
            // partial fill, start gap end gap
375
            string text = node.InnerText;
376
            string textBefore = text.Substring(0, selectionStart - start);
377
            string textAfter = text.Substring(selectionStart - start + tag.Length);
378
            string textSelected = text.Substring(selectionStart - start, tag.Length);
379

    
380
            var parentNode = node.ParentNode;
381
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
382
            parentNode.ChildNodes.RemoveAt(nodeIndex);
383

    
384
            int spanBeforeStart = start;
385
            int spanSelectedStart = start + textBefore.Length;
386
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
387

    
388
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
389
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
390

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

    
394
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
395
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
396

    
397
            return new() { spanSelected, spanBefore, spanAfter };
398
        }
399

    
400
        private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE)
401
        {
402
            HtmlNode span = doc.CreateElement("span");
403
            span.InnerHtml = text;
404
            TagStartPositions.Add(startPosition);
405
            TagStartLengths.Add(0);
406
            TagClosingPositions.Add(startPosition + text.Length);
407
            TagClosingLengths.Add(0);
408
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
409

    
410
            if (instanceId != null)
411
            {
412
                span.AddClass("annotation");
413
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
414
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
415

    
416
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
417
                {
418
                    span.Attributes.Add("start", "1");
419
                }
420
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
421
                {
422
                    span.Attributes.Add("end", "1");
423
                }
424
            }
425

    
426
            return span;
427
        }
428

    
429
        private enum EPosition
430
        {
431
            MARK_LEFT = 5,
432
            MARK_RIGHT = 3,
433
            MARK_LEFT_RIGHT = 2,
434
            MARK_NONE = 0
435
        }
436

    
437
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
438
        {
439
            foreach (var node in descendantsOriginal)
440
            {
441
                var originalNode = node;
442
                var toEditNode = NodeDict[node];
443

    
444
                if (originalNode.Name.Contains("#"))
445
                {
446
                    continue;
447
                }
448
                else
449
                {
450
                    bool onlyText = true;
451
                    bool onlySubtags = true;
452

    
453
                    foreach (var child in node.ChildNodes)
454
                    {
455
                        if (child.Name.Contains("#"))
456
                        {
457
                            onlySubtags = false;
458
                        }
459
                        else
460
                        {
461
                            onlyText = false;
462
                        }
463
                    }
464

    
465
                    if (onlyText || onlySubtags)
466
                    {
467
                        continue;
468
                    }
469
                    else
470
                    {
471

    
472
                        foreach (var child in node.ChildNodes)
473
                        {
474
                            if (child.Name.Contains("#text"))
475
                            {
476
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
477
                                coveringSpan.InnerHtml = child.InnerHtml;
478
                                TagStartPositions.Add(child.InnerStartIndex);
479
                                TagStartLengths.Add(0);
480
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
481
                                TagClosingLengths.Add(0);
482
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
483

    
484
                                var parent = NodeDict[node];
485
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
486

    
487
                                parent.ChildNodes.RemoveAt(index);
488
                                parent.ChildNodes.Insert(index, coveringSpan);
489
                            }
490
                        }
491
                    }
492
                }
493
            }
494
        }
495

    
496
        private HtmlNode GenerateCSS(HtmlDocument docToEdit, List<AnnotationTag> tags, List<int> paddings)
497
        {
498
            HtmlNode style = docToEdit.CreateElement("style");
499

    
500
            string inner = "span.annotation {border-bottom: 2px solid;}";
501
            inner += "span {line-height: 30px}\n";
502

    
503
            // TODO temporary
504
            int lastPadding = 0;
505
            foreach (var tag in tags)
506
            {
507
                inner += $"span[{TAG_INSTANCE_ATTRIBUTE_NAME}=\"{tag.Instance}\"] {{ border-color:{tag.Tag.Color}; padding-bottom: {lastPadding % 5}px }}";
508
                lastPadding += 2;
509
            }
510

    
511
            inner += "span[end=\"1\"] {border-end-end-radius: 0px; border-right: 1px solid darkgray}\n";
512
            inner += "span[start=\"1\"] {border-end-start-radius: 0px; border-left: 1px solid darkgray}\n";
513

    
514
            style.InnerHtml = inner;
515
            return style;
516
        }
517

    
518
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
519
        {
520
            foreach (var node in descendantsOriginal)
521
            {
522
                var originalNode = node;
523
                var toEditNode = NodeDict[node];
524

    
525
                if (originalNode.Name.Contains("#"))
526
                {
527
                    continue;
528
                }
529
                else
530
                {
531
                    TagStartPositions.Add(originalNode.OuterStartIndex);
532
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
533
                    currentId = TagStartPositions.Count - 1;
534
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
535

    
536
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
537
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
538
                }
539
            }
540
        }
541

    
542
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
543
        {
544
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
545
            {
546
                Original = orig,
547
                ToEdit = toEdit
548
            });
549
            foreach (var node in zipped)
550
            {
551
                var originalNode = node.Original;
552
                var toEditNode = node.ToEdit;
553
                NodeDict.Add(originalNode, toEditNode);
554
            }
555
        }
556

    
557

    
558
        /*
559
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
560
         */
561

    
562
        /*
563
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
564
         */
565

    
566
        private string PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTag> tags)
567
        {
568
            var docOriginal = new HtmlDocument();
569
            docOriginal.LoadHtml(htmlOriginal);
570
            var docToEdit = new HtmlDocument();
571
            docToEdit.LoadHtml(htmlToEdit);
572

    
573
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
574
            RemoveCSS(docToEdit);
575

    
576
            int i = 0;
577
            List<HtmlNode> addedForSelection = new();
578

    
579
            int descendantsCount = descendantsToEdit.Count();
580
            while (i < descendantsCount)
581
            {
582
                for (; i < descendantsCount; i++)
583
                {
584
                    var node = descendantsToEdit.ElementAt(i);
585
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
586
                        node.ParentNode.Name == "style")
587
                    {
588
                        continue;
589
                    }
590

    
591
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
592

    
593
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
594
                    var end = TagClosingPositions[nodeId];
595

    
596
                    int selectionStart = tagToAdd.Position;
597
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
598

    
599
                    if (selectionStart < end && selectionEnd > start)
600
                    {
601
                        if (selectionStart <= start && selectionEnd >= end)
602
                        {
603
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
604
                        }
605
                        else if (selectionStart <= start)
606
                        {
607
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
608
                        }
609
                        else if (selectionEnd >= end)
610
                        {
611
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
612
                        }
613
                        else
614
                        {
615
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
616
                        }
617
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
618
                        descendantsCount = descendantsToEdit.Count();
619
                        break;
620
                    }
621
                }
622
            }
623

    
624
            var cssNode = GenerateCSS(docToEdit, tags, new());
625
            docToEdit.DocumentNode.ChildNodes.Insert(0, cssNode);
626

    
627
            return docToEdit.DocumentNode.OuterHtml;
628
        }
629

    
630

    
631
        private string PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTag> tags)
632
        {
633
            var docOriginal = new HtmlDocument();
634
            docOriginal.LoadHtml(htmlOriginal);
635
            var docToEdit = new HtmlDocument();
636
            docToEdit.LoadHtml(htmlToEdit);
637

    
638
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
639
            RemoveCSS(docToEdit);
640

    
641
            int i = 0;
642
            int descendantsCount = descendantsToEdit.Count();
643
            while (i < descendantsCount)
644
            {
645
                for (; i < descendantsCount; i++)
646
                {
647
                    var node = descendantsToEdit.ElementAt(i);
648
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
649
                    {
650
                        continue;
651
                    }
652
                    else
653
                    {
654
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
655
                        {
656
                            continue;
657
                        }
658

    
659
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
660
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
661
                        node.Attributes.Remove("class");
662
                        node.Attributes.Remove("start");
663
                        node.Attributes.Remove("end");
664

    
665
                        /*var parent = node.ParentNode;
666
                        var contents = node.ChildNodes;
667
                        int index = parent.ChildNodes.IndexOf(node);
668
                        parent.ChildNodes.RemoveAt(index);
669

    
670
                        List<HtmlNode> newChildren = new();
671
                        for (int j = 0; j < index; j++)
672
                        {
673
                            newChildren.Add(parent.ChildNodes[j]);
674
                        }
675
                        for (int j = 0; j < contents.Count; j++)
676
                        {
677
                            newChildren.Add(contents[j]);
678
                        }
679
                        for (int j = index; j < parent.ChildNodes.Count; j++)
680
                        {
681
                            newChildren.Add(parent.ChildNodes[j]);
682
                        }
683

    
684
                        parent.ChildNodes.Clear();
685
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
686

    
687
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
688
                        descendantsCount = descendantsToEdit.Count();
689
                        break;
690
                    }
691
                }
692
            }
693

    
694
            var cssNode = GenerateCSS(docToEdit, tags, new());
695
            docToEdit.DocumentNode.ChildNodes.Insert(0, cssNode);
696

    
697
            return docToEdit.DocumentNode.OuterHtml;
698
        }
699

    
700
        private void RemoveCSS(HtmlDocument doc)
701
        {
702
            doc.DocumentNode.ChildNodes.RemoveAt(0);
703
        }
704

    
705
        /*
706
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
707
         */
708

    
709

    
710
        // TODO temporary
711
        private bool IsHtml(string text)
712
        {
713
            return text.Contains("<html>");
714
        }
715

    
716
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
717
        {
718
            var annotation = context.Annotations
719
               .Where(a => a.Id == annotationId)
720
               .Include(a => a.User)
721
               .Include(a => a.Document).ThenInclude(d => d.Content)
722
               .First();
723

    
724
            if (userRole < ERole.ADMINISTRATOR)
725
            {
726
                if (annotation.User.Id != userId)
727
                {
728
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
729
                }
730
            }
731

    
732
            AnnotationTag annotationTag = new()
733
            {
734
                Id = Guid.NewGuid(),
735
                Annotation = annotation,
736
                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
737
                Length = request.Length,
738
                Position = request.Position,
739
                Note = ""
740
            };
741

    
742
            if (request.Type == ETagType.TAG)
743
            {
744
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
745
                annotationTag.SubTag = null;
746

    
747
                // If for the same annotation exists a tag with same position and length and of the same type, ignore
748
                if (context.AnnotationTags.Any(at => 
749
                at.Position == annotationTag.Position &&
750
                at.Length == annotationTag.Length &&
751
                at.Annotation == annotation &&
752
                at.Tag == annotationTag.Tag))
753
                {
754
                    throw new InvalidOperationException("Duplicate tag");
755
                }
756

    
757
            }
758
            else if (request.Type == ETagType.SUBTAG)
759
            {
760
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
761
                annotationTag.SubTag = subTag;
762
                annotationTag.Tag = subTag.Tag;
763

    
764
                if (context.AnnotationTags.Any(at => 
765
                at.Position == annotationTag.Position &&
766
                at.Length == annotationTag.Length &&
767
                at.Annotation == annotation &&
768
                at.Tag == annotationTag.Tag &&
769
                at.SubTag == annotationTag.SubTag))
770
                {
771
                    throw new InvalidOperationException("Duplicate tag");
772
                }
773
            }
774
            else
775
            {
776
                throw new ArgumentException($"Unknown tag type {request.Type}");
777
            }
778

    
779
            annotation.LastModifiedTagId = annotationTag.Id;
780
            annotation.ModifiedType = EModified.ADDED;
781

    
782
            context.AnnotationTags.Add(annotationTag);
783
            context.SaveChanges();
784
        }
785

    
786
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
787
        {
788
            Annotation annotation = null;
789
            try
790
            {
791
                annotation = context.Annotations
792
                   .Where(a => a.Id == annotationId)
793
                   .Include(a => a.User)
794
                   .Include(a => a.Document).ThenInclude(d => d.Content)
795
                   .First();
796

    
797
            }
798
            catch (Exception ex)
799
            {
800
                throw new InvalidOperationException("Could not find annotation");
801
            }
802

    
803

    
804
            if (userRole < ERole.ADMINISTRATOR)
805
            {
806
                if (annotation.User.Id != loggedUserId)
807
                {
808
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
809
                }
810
            }
811

    
812
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
813
            {
814
                throw new InvalidOperationException("Could not find tag instance");
815
            }
816

    
817

    
818
            var annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
819
            annotation.LastModifiedTagId = annotationTag.Id;
820
            annotation.ModifiedType = EModified.REMOVED;
821

    
822
            context.AnnotationTags.Remove(annotationTag);
823

    
824
            context.SaveChanges();
825
        }
826
    }
827
}
(1-1/2)