Projekt

Obecné

Profil

Stáhnout (33.7 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 AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole)
104
        {
105
            var annotation = context.Annotations
106
                .Where(a => a.Id == annotationId)
107
                .Include(a => a.User)
108
                .Include(a => a.Document).ThenInclude(d => d.Content)
109
                .First();
110

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

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

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

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

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

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

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

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

    
192
            NodeDict.Clear();
193

    
194
            return annotationInfo;
195
        }
196

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

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

    
207

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

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

    
218
            int currentId = 0;
219

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

    
223
            WrapTextInSpan(descendantsOriginal, docToEdit);
224

    
225
            int descCount = descendantsToEdit.Count;
226

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

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

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

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

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

    
275
            }
276

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

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

    
300
            return doc.DocumentNode.OuterHtml;
301
        }
302

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

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

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

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

    
321
            return spanSelected;
322
        }
323

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
427
            return span;
428
        }
429

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

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

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

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

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

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

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

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

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

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

    
504
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
505

    
506
            foreach (var tag in tags)
507
            {
508
                var padding = (tagPaddingDict[tag] + 1) * 2;
509
                inner += $"span[{TAG_INSTANCE_ATTRIBUTE_NAME}=\"{tag.Instance}\"] {{ border-color:{tag.Tag.Color}; padding-bottom: {padding}px }}";
510
            }
511

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

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

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

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

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

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

    
558

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

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

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

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

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

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

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

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

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

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

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

    
628
            return docToEdit.DocumentNode.OuterHtml;
629
        }
630

    
631

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

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

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

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

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

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

    
685
                        parent.ChildNodes.Clear();
686
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
687
                        
688
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
689
                        descendantsCount = descendantsToEdit.Count();
690
                        break;
691
                    }
692
                }
693
            }
694

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

    
698
            return docToEdit.DocumentNode.OuterHtml;
699
        }
700

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

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

    
710

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

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

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

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

    
743
            if (request.Type == ETagType.TAG)
744
            {
745
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
746
                annotationTag.SubTag = null;
747
            }
748
            else if (request.Type == ETagType.SUBTAG)
749
            {
750
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
751
                annotationTag.SubTag = subTag;
752
                annotationTag.Tag = subTag.Tag;
753
            }
754
            else
755
            {
756
                throw new ArgumentException($"Unknown tag type {request.Type}");
757
            }
758

    
759
            annotation.LastModifiedTagId = annotationTag.Id;
760
            annotation.ModifiedType = EModified.ADDED;
761

    
762
            context.AnnotationTags.Add(annotationTag);
763
            context.SaveChanges();
764
        }
765

    
766
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
767
        {
768
            Annotation annotation = null;
769
            try
770
            {
771
                annotation = context.Annotations
772
                   .Where(a => a.Id == annotationId)
773
                   .Include(a => a.User)
774
                   .Include(a => a.Document).ThenInclude(d => d.Content)
775
                   .First();
776

    
777
            }
778
            catch (Exception ex)
779
            {
780
                throw new InvalidOperationException("Could not find annotation");
781
            }
782

    
783

    
784
            if (userRole < ERole.ADMINISTRATOR)
785
            {
786
                if (annotation.User.Id != loggedUserId)
787
                {
788
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
789
                }
790
            }
791

    
792
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
793
            {
794
                throw new InvalidOperationException("Could not find tag instance");
795
            }
796

    
797

    
798
            var annotationTag = context.AnnotationTags.First(at => at.Id == tagInstanceId);
799
            annotation.LastModifiedTagId = annotationTag.Id;
800
            annotation.ModifiedType = EModified.REMOVED;
801

    
802
            context.AnnotationTags.Remove(annotationTag);
803

    
804
            context.SaveChanges();
805
        }
806
    }
807
}
(1-1/2)