Projekt

Obecné

Profil

Stáhnout (25.1 KB) Statistiky
| Větev: | Tag: | Revize:
1 eff8ec56 Vojtěch Bartička
using Core.Contexts;
2
using Core.Entities;
3
using Models.Annotations;
4
using Models.Enums;
5
using Serilog;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9
using System.Text;
10
using System.Threading.Tasks;
11 6bdb3d95 Vojtěch Bartička
using Microsoft.EntityFrameworkCore;
12 a6675a6d Vojtěch Bartička
using AutoMapper;
13
using Models.Tags;
14 3c185841 Vojtěch Bartička
using Ganss.XSS;
15
using HtmlAgilityPack;
16
using System.Text.RegularExpressions;
17 eff8ec56 Vojtěch Bartička
18
namespace Core.Services.AnnotationService
19
{
20
    public class AnnotationServiceEF : IAnnotationService
21
    {
22
        private readonly DatabaseContext context;
23
        private readonly ILogger logger;
24 a6675a6d Vojtěch Bartička
        private readonly IMapper mapper;
25 eff8ec56 Vojtěch Bartička
26 0a9f9349 Vojtěch Bartička
        private const string TAG_ID_ATTRIBUTE_NAME = "aswi-tag-id";
27 c9762683 Vojtěch Bartička
        private const string TAG_INSTANCE_ATTRIBUTE_NAME = "aswi-tag-instance";
28 0a9f9349 Vojtěch Bartička
29 a6675a6d Vojtěch Bartička
        public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper)
30 eff8ec56 Vojtěch Bartička
        {
31
            this.context = context;
32
            this.logger = logger;
33 a6675a6d Vojtěch Bartička
            this.mapper = mapper;
34 eff8ec56 Vojtěch Bartička
        }
35
36
        public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId)
37
        {
38
            User addingUser = context.Users.Single(u => u.Id == clientUserId);
39
40
            // Check the documents exist
41 153d77a8 Vojtěch Bartička
            var documents = context.Documents.Where(d => request.DocumentIdList.Contains(d.Id)).ToList();
42
            if (documents.Count() != request.DocumentIdList.Count)
43 eff8ec56 Vojtěch Bartička
            {
44 153d77a8 Vojtěch Bartička
                logger.Information($"Received a non-existent Document ID when assigning documents to users");
45
                throw new InvalidOperationException($"{request.DocumentIdList.Count - documents.Count()} of the received documents do not exist");
46 eff8ec56 Vojtěch Bartička
            }
47
48 6bdb3d95 Vojtěch Bartička
            var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList();
49 153d77a8 Vojtěch Bartička
            foreach (var user in users)
50 eff8ec56 Vojtěch Bartička
            {
51 153d77a8 Vojtěch Bartička
                var userAnnotatedDocuments = context.Annotations.Where(a => a.User == user).Select(a => a.Document).ToList();
52
                foreach (var doc in documents)
53 eff8ec56 Vojtěch Bartička
                {
54 153d77a8 Vojtěch Bartička
                    if (userAnnotatedDocuments.Contains(doc))
55 eff8ec56 Vojtěch Bartička
                    {
56 153d77a8 Vojtěch Bartička
                        logger.Information($"User {user.Username} has already been assigned the document {doc.Id}, ignoring");
57
                        continue;
58 eff8ec56 Vojtěch Bartička
                    }
59
60
                    context.Annotations.Add(new Annotation()
61
                    {
62
                        User = user,
63
                        UserAssigned = addingUser,
64
                        DateAssigned = DateTime.Now,
65
                        DateLastChanged = DateTime.Now,
66 153d77a8 Vojtěch Bartička
                        Document = doc,
67 eff8ec56 Vojtěch Bartička
                        State = EState.NEW,
68
                        Note = ""
69
                    });
70
                }
71
            }
72
73
            context.SaveChanges();
74
        }
75 6bdb3d95 Vojtěch Bartička
76
        public AnnotationListResponse GetUserAnnotations(Guid userId)
77
        {
78
            var annotations = context.Annotations.Where(a => a.User.Id == userId).Include(a => a.Document).ToList();
79
            var documentIds = annotations.Select(a => a.Document.Id).ToList();
80
            var documents = context.Documents.Where(d => documentIds.Contains(d.Id));
81
            var infos = new List<AnnotationListInfo>();
82
83
            var annotationsDocuments = annotations.Zip(documents, (a, d) => new { Annotation = a, Document = d });
84
            foreach (var ad in annotationsDocuments)
85
            {
86
                infos.Add(new AnnotationListInfo()
87
                {
88
                    AnnotationId = ad.Annotation.Id,
89
                    DocumentName = ad.Document.Name,
90
                    State = ad.Annotation.State
91
                });
92
            }
93
94
            return new AnnotationListResponse()
95
            {
96
                Annotations = infos
97
            };
98
        }
99 a6675a6d Vojtěch Bartička
100 f2275185 Vojtěch Bartička
        public void AddNoteToAnnotation(Guid annotationId, Guid userId, ERole userRole, AddNoteToAnnotationRequest request)
101
        {
102
            Annotation annotation = null;
103
            try
104
            {
105
                annotation = context.Annotations.Include(a => a.User).First(a => a.Id == annotationId);
106
            }
107
            catch (Exception)
108
            {
109
                throw new InvalidOperationException("Annotation not found");
110
            }
111
112
            if (userRole < ERole.ADMINISTRATOR && annotation.User.Id != userId)
113
            {
114
                throw new UnauthorizedAccessException("User does not have access to this annotation");
115
            }
116
117
            annotation.Note = request.Note;
118
            context.SaveChanges();
119
        }
120
121
122 a6675a6d Vojtěch Bartička
        public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole)
123
        {
124 c2a89232 Vojtěch Bartička
            var annotation = context.Annotations
125
                .Where(a => a.Id == annotationId)
126
                .Include(a => a.User)
127
                .Include(a => a.Document).ThenInclude(d => d.Content)
128
                .First();
129 3c185841 Vojtěch Bartička
130 a6675a6d Vojtěch Bartička
            if (userRole < ERole.ADMINISTRATOR)
131
            {
132
                if (annotation.User.Id != userId)
133
                {
134
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
135
                }
136
            }
137
138 c2a89232 Vojtěch Bartička
            var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First();
139 a6675a6d Vojtěch Bartička
140 c2a89232 Vojtěch Bartička
            var tags = context.AnnotationTags.Where(at => at.Annotation.Id == annotationId)
141
                .Include(at => at.Tag).ThenInclude(t => t.Category)
142
                .Include(at => at.SubTag)
143
                .ToList();
144
145 0a9f9349 Vojtěch Bartička
            List<TagInstanceInfo> tagInstanceInfos = new();
146 a6675a6d Vojtěch Bartička
            foreach (var tag in tags)
147
            {
148
                var tagInstance = mapper.Map<TagInstanceInfo>(tag);
149 0a9f9349 Vojtěch Bartička
                tagInstanceInfos.Add(tagInstance);
150 a6675a6d Vojtěch Bartička
            }
151
152 0a9f9349 Vojtěch Bartička
            var docToRender = PreprocessHTML(documentContent.Content, tags);
153
154
            // We probably cannot use AutoMapper since we are dealing with too many different entities
155
            AnnotationInfo annotationInfo = new()
156
            {
157
                SourceDocumentContent = documentContent.Content,
158
                DocumentToRender = docToRender,
159
                TagStartPositions = TagStartPositions.ToArray(),
160
                TagLengths = TagStartLengths.ToArray(),
161
                Note = annotation.Note,
162
                State = annotation.State,
163
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
164
                TagInstances = tagInstanceInfos
165
            };
166
167 a6675a6d Vojtěch Bartička
            return annotationInfo;
168
        }
169
170 0a9f9349 Vojtěch Bartička
        private List<int> TagStartPositions = new();
171
        private List<int> TagStartLengths = new();
172
        private List<int> TagClosingPositions = new();
173
        private List<int> TagClosingLengths = new();
174
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
175
176
        private string PreprocessHTML(string htmlSource, List<AnnotationTag> tags)
177 3c185841 Vojtěch Bartička
        {
178
            var docOriginal = new HtmlDocument();
179
            docOriginal.LoadHtml(htmlSource);
180
            var docToEdit = new HtmlDocument();
181
            docToEdit.LoadHtml(htmlSource);
182
183
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
184
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf();
185
186 0a9f9349 Vojtěch Bartička
            int currentId = 0;
187 3c185841 Vojtěch Bartička
188 0a9f9349 Vojtěch Bartička
            FillNodeDict(descendantsOriginal, descendantsToEdit);
189
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
190 3c185841 Vojtěch Bartička
191 0a9f9349 Vojtěch Bartička
            WrapTextInSpan(descendantsOriginal, docToEdit);
192 3c185841 Vojtěch Bartička
193 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
194 3c185841 Vojtěch Bartička
            {
195 0a9f9349 Vojtěch Bartička
                int i = 0;
196
                List<HtmlNode> addedForSelection = new();
197 c9762683 Vojtěch Bartička
                while (i < descendantsToEdit.Count())
198 3c185841 Vojtěch Bartička
                {
199 c9762683 Vojtěch Bartička
                    for (; i < descendantsToEdit.Count(); i++)
200 0a9f9349 Vojtěch Bartička
                    {
201
                        var node = descendantsToEdit.ElementAt(i);
202
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
203
                            node.ParentNode.Name == "style")
204
                        {
205
                            continue;
206
                        }
207
208 c9762683 Vojtěch Bartička
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
209
210
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
211
                        var end = TagClosingPositions[nodeId];
212 0a9f9349 Vojtěch Bartička
213
                        int selectionStart = tag.Position;
214
                        int selectionEnd = tag.Position + tag.Length;
215
216
                        if (selectionStart < end && selectionEnd > start)
217
                        {
218
                            if (selectionStart <= start && selectionEnd >= end)
219
                            {
220
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
221
                            }
222
                            else if (selectionStart <= start)
223
                            {
224
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
225
                            }
226
                            else if (selectionEnd >= end)
227
                            {
228
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
229
                            }
230
                            else
231
                            {
232
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
233
                            }
234
                            break;
235
                        }
236
                    }
237 3c185841 Vojtěch Bartička
                }
238
239
            }
240
241
            string docToRender = docToEdit.DocumentNode.OuterHtml;
242
            HtmlSanitizer sanitizer = new HtmlSanitizer();
243
            sanitizer.AllowedAttributes.Clear();
244 0a9f9349 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
245
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
246 c9762683 Vojtěch Bartička
            sanitizer.AllowedAttributes.Add("end");
247
            sanitizer.AllowedAttributes.Add("start");
248
            sanitizer.AllowedAttributes.Add("class");
249 be4deff8 Vojtěch Bartička
            if (sanitizer.AllowedTags.Contains("script"))
250
            {
251
                sanitizer.AllowedTags.Remove("script");
252 f2275185 Vojtěch Bartička
            }
253 c9762683 Vojtěch Bartička
            if (!sanitizer.AllowedTags.Contains("style"))
254
            {
255
                sanitizer.AllowedTags.Add("style");
256 be4deff8 Vojtěch Bartička
            }
257 3c185841 Vojtěch Bartička
            docToRender = sanitizer.Sanitize(docToRender);
258
259 c9762683 Vojtěch Bartička
            HtmlDocument doc = new HtmlDocument();
260
            doc.LoadHtml(docToRender);
261
            var cssNode = GenerateCSS(doc, tags, new());
262
            doc.DocumentNode.ChildNodes.Insert(0, cssNode);
263
264
            return doc.DocumentNode.OuterHtml;
265 0a9f9349 Vojtěch Bartička
        }
266
267
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
268
        {
269
            // full fill
270
            string textSelected = node.InnerText;
271
272
            var parentNode = node.ParentNode;
273
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
274
            parentNode.ChildNodes.RemoveAt(nodeIndex);
275
276
            EPosition markerPosition = EPosition.MARK_NONE;
277
            if (selectionEnd == end && selectionStart == start)
278
            {
279
                markerPosition = EPosition.MARK_LEFT_RIGHT;
280
            }
281
282
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
283
                TAG_INSTANCE_ATTRIBUTE_NAME, tag.Instance, start, markerPosition);
284
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
285
286
            return spanSelected;
287
        }
288
289
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
290
                                             AnnotationTag tag)
291
        {
292
            // partial fill, end gap
293
            string text = node.InnerText;
294
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
295
            string textSelected = text.Substring(0, selectionEnd - start);
296
297
            var parentNode = node.ParentNode;
298
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
299
            parentNode.ChildNodes.RemoveAt(nodeIndex);
300
301
            int spanSelectedStart = start;
302
            int spanAfterStart = start + textSelected.Length;
303
304
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
305
                TAG_INSTANCE_ATTRIBUTE_NAME, tag.Instance, spanSelectedStart, EPosition.MARK_RIGHT);
306
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
307
308
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
309
                TAG_INSTANCE_ATTRIBUTE_NAME, null, spanAfterStart);
310
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
311
312
            return new() { spanSelected, spanAfter };
313
        }
314
315
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
316
                                             AnnotationTag tag)
317
        {
318
            // partial fill, start gap
319
            string text = node.InnerText;
320
            string textBefore = text.Substring(0, selectionStart - start);
321
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
322
323
            var parentNode = node.ParentNode;
324
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
325
            parentNode.ChildNodes.RemoveAt(nodeIndex);
326
327
            int spanBeforeStart = start;
328
            int spanSelectedStart = start + textBefore.Length;
329
330
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
331
                TAG_INSTANCE_ATTRIBUTE_NAME, null, spanBeforeStart);
332
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
333
334
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
335
                TAG_INSTANCE_ATTRIBUTE_NAME, tag.Instance, spanSelectedStart, EPosition.MARK_LEFT);
336
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
337
338
            return new() { spanSelected, spanBefore };
339
        }
340
341
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
342
                                                 AnnotationTag tag)
343
        {
344
            // partial fill, start gap end gap
345
            string text = node.InnerText;
346
            string textBefore = text.Substring(0, selectionStart - start);
347
            string textAfter = text.Substring(selectionStart - start + tag.Length);
348
            string textSelected = text.Substring(selectionStart - start, tag.Length);
349
350
            var parentNode = node.ParentNode;
351
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
352
            parentNode.ChildNodes.RemoveAt(nodeIndex);
353
354
            int spanBeforeStart = start;
355
            int spanSelectedStart = start + textBefore.Length;
356
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
357
358
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
359
                TAG_INSTANCE_ATTRIBUTE_NAME, null, spanBeforeStart);
360
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
361
362
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
363
                TAG_INSTANCE_ATTRIBUTE_NAME, tag.Instance, spanSelectedStart, EPosition.MARK_LEFT_RIGHT);
364
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanSelected);
365
366
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TAG_ID_ATTRIBUTE_NAME, TagStartPositions.Count,
367
                TAG_INSTANCE_ATTRIBUTE_NAME, null, spanAfterStart);
368
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
369
370
            return new() { spanSelected, spanBefore, spanAfter };
371
        }
372
373
        private HtmlNode CreateSpan(HtmlDocument doc, string text, string tagIdAttributeName, int tagId, string tagInstanceAttributeName,
374
                                            Guid? instanceId, int startPosition, EPosition position = EPosition.MARK_NONE)
375
        {
376
            HtmlNode span = doc.CreateElement("span");
377
            span.InnerHtml = text;
378
            TagStartPositions.Add(startPosition);
379
            TagStartLengths.Add(0);
380
            TagClosingPositions.Add(startPosition + text.Length);
381
            TagClosingLengths.Add(0);
382
            span.Attributes.Add(tagIdAttributeName, tagId.ToString());
383
384
            if (instanceId != null)
385
            {
386
                span.AddClass("annotation");
387
                span.Attributes.Add(tagInstanceAttributeName, instanceId.Value.ToString());
388
389
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
390
                {
391
                    span.Attributes.Add("start", "1");
392
                }
393
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
394
                {
395
                    span.Attributes.Add("end", "1");
396
                }
397
            }
398
399
            return span;
400
        }
401
402
        private enum EPosition
403
        {
404
            MARK_LEFT = 5,
405
            MARK_RIGHT = 3,
406
            MARK_LEFT_RIGHT = 2,
407
            MARK_NONE = 0
408
        }
409
410
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
411
        {
412
            foreach (var node in descendantsOriginal)
413
            {
414
                var originalNode = node;
415
                var toEditNode = NodeDict[node];
416
417
                if (originalNode.Name.Contains("#"))
418
                {
419
                    continue;
420
                }
421
                else
422
                {
423
                    bool onlyText = true;
424
                    bool onlySubtags = true;
425
426
                    foreach (var child in node.ChildNodes)
427
                    {
428
                        if (child.Name.Contains("#"))
429
                        {
430
                            onlySubtags = false;
431
                        }
432
                        else
433
                        {
434
                            onlyText = false;
435
                        }
436
                    }
437
438
                    if (onlyText || onlySubtags)
439
                    {
440
                        continue;
441
                    }
442
                    else
443
                    {
444
445
                        foreach (var child in node.ChildNodes)
446
                        {
447
                            if (child.Name.Contains("#text"))
448
                            {
449
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
450
                                coveringSpan.InnerHtml = child.InnerHtml;
451
                                TagStartPositions.Add(child.InnerStartIndex);
452
                                TagStartLengths.Add(0);
453
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
454
                                TagClosingLengths.Add(0);
455
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
456
457
                                var parent = NodeDict[node];
458
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
459
460
                                parent.ChildNodes.RemoveAt(index);
461
                                parent.ChildNodes.Insert(index, coveringSpan);
462
                            }
463
                        }
464
                    }
465
                }
466
            }
467
        }
468
469
        private HtmlNode GenerateCSS(HtmlDocument docToEdit, List<AnnotationTag> tags, List<int> paddings)
470
        {
471
            HtmlNode style = docToEdit.CreateElement("style");
472
473
            string inner = "span.annotation {border-bottom: 2px solid;}";
474
            inner += "span {line-height: 30px}\n";
475
476
            // TODO temporary
477 c9762683 Vojtěch Bartička
            int lastPadding = 0;
478 0a9f9349 Vojtěch Bartička
            foreach (var tag in tags)
479
            {
480 c9762683 Vojtěch Bartička
                inner += $"span[{TAG_INSTANCE_ATTRIBUTE_NAME}=\"{tag.Instance}\"] {{ border-color:{tag.Tag.Color}; padding-bottom: {lastPadding % 5}px }}";
481 0a9f9349 Vojtěch Bartička
                lastPadding += 2;
482
            }
483
484
            inner += "span[end=\"1\"] {border-end-end-radius: 0px; border-right: 1px solid darkgray}\n";
485
            inner += "span[start=\"1\"] {border-end-start-radius: 0px; border-left: 1px solid darkgray}\n";
486
487
            style.InnerHtml = inner;
488
            return style;
489
        }
490
491
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
492
        {
493
            foreach (var node in descendantsOriginal)
494
            {
495
                var originalNode = node;
496
                var toEditNode = NodeDict[node];
497
498
                if (originalNode.Name.Contains("#"))
499
                {
500
                    continue;
501
                }
502
                else
503
                {
504
                    TagStartPositions.Add(originalNode.OuterStartIndex);
505
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
506
                    currentId = TagStartPositions.Count - 1;
507
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
508
509
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
510
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
511
                }
512
            }
513
        }
514
515
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
516
        {
517
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
518
            {
519
                Original = orig,
520
                ToEdit = toEdit
521
            });
522
            foreach (var node in zipped)
523
            {
524
                var originalNode = node.Original;
525
                var toEditNode = node.ToEdit;
526
                NodeDict.Add(originalNode, toEditNode);
527
            }
528 3c185841 Vojtěch Bartička
        }
529
530 a6675a6d Vojtěch Bartička
        // TODO temporary
531
        private bool IsHtml(string text)
532
        {
533 0a9f9349 Vojtěch Bartička
            return text.Contains("<html>");
534 a6675a6d Vojtěch Bartička
        }
535 be4deff8 Vojtěch Bartička
536
        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
537
        {
538
            var annotation = context.Annotations
539
               .Where(a => a.Id == annotationId)
540
               .Include(a => a.User)
541
               .Include(a => a.Document).ThenInclude(d => d.Content)
542
               .First();
543
544
            if (userRole < ERole.ADMINISTRATOR)
545
            {
546
                if (annotation.User.Id != userId)
547
                {
548
                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
549
                }
550
            }
551
552
            AnnotationTag annotationTag = new()
553
            {
554
                Annotation = annotation,
555
                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
556
                Length = request.Length,
557
                Position = request.Position,
558
                Note = ""
559
            };
560
561
            if (request.Type == ETagType.TAG)
562
            {
563
                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
564
                annotationTag.SubTag = null;
565
            }
566
            else if (request.Type == ETagType.SUBTAG)
567
            {
568
                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
569
                annotationTag.SubTag = subTag;
570
                annotationTag.Tag = subTag.Tag;
571
            }
572
            else
573
            {
574
                throw new ArgumentException($"Unknown tag type {request.Type}");
575
            }
576
577 abf94f21 Lukáš Vlček
            context.AnnotationTags.Add(annotationTag);
578 be4deff8 Vojtěch Bartička
            context.SaveChanges();
579
        }
580 0f8d6304 Vojtěch Bartička
581
        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
582
        {
583
            Annotation annotation = null;
584
            try
585
            {
586
                annotation = context.Annotations
587
                   .Where(a => a.Id == annotationId)
588
                   .Include(a => a.User)
589
                   .Include(a => a.Document).ThenInclude(d => d.Content)
590
                   .First();
591
592
            }
593
            catch (Exception ex)
594
            {
595
                throw new InvalidOperationException("Could not find annotation");
596
            }
597
598
599
            if (userRole < ERole.ADMINISTRATOR)
600
            {
601
                if (annotation.User.Id != loggedUserId)
602
                {
603
                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
604
                }
605
            }
606
607
            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
608
            {
609
                throw new InvalidOperationException("Could not find tag instance");
610
            }
611
612
            context.AnnotationTags
613
                .Where(at => at.Id == tagInstanceId).ToList()
614
                .ForEach(a => context.AnnotationTags.Remove(a));
615 0a9f9349 Vojtěch Bartička
616 0f8d6304 Vojtěch Bartička
            context.SaveChanges();
617
        }
618 eff8ec56 Vojtěch Bartička
    }
619
}