Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 2c9afc72

Přidáno uživatelem Vojtěch Bartička před asi 2 roky(ů)

Moved HTML processing to HTMLService, inheritance changes in entities

Zobrazit rozdíly:

Backend/Core/Entities/AnnotationTag.cs
9 9

  
10 10
namespace Core.Entities
11 11
{
12
    public class AnnotationTag : BaseEntity
12
    public class AnnotationTag : AnnotationTagGeneric
13 13
    {
14 14
        public Annotation Annotation { get; set; }
15

  
16
        public Tag Tag { get; set; }
17

  
18
        /** Nullable for optional */
19
        public SubTag? SubTag { get; set; }
20

  
21
        public Guid Instance { get; set; }
22
        public string Note { get; set; }
23
        public int Position { get; set; }
24
        public int Length { get; set; }
25
        public ETagSentiment? Sentiment { get; set; }
26
        public string SelectedText { get; set; }
27 15
    }
28 16
}
Backend/Core/Entities/AnnotationTagGeneric.cs
1
using Models.Enums;
2
using System;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Text;
6
using System.Threading.Tasks;
7

  
8
namespace Core.Entities
9
{
10
    public class AnnotationTagGeneric : BaseEntity
11
    {
12
        public Tag Tag { get; set; }
13

  
14
        /** Nullable for optional */
15
        public SubTag? SubTag { get; set; }
16

  
17
        public Guid Instance { get; set; }
18
        public string Note { get; set; }
19
        public int Position { get; set; }
20
        public int Length { get; set; }
21
        public ETagSentiment? Sentiment { get; set; }
22
        public string SelectedText { get; set; }
23
    }
24
}
Backend/Core/Entities/FinalAnnotationTag.cs
6 6

  
7 7
namespace Core.Entities
8 8
{
9
    public class FinalAnnotationTag : AnnotationTag
9
    public class FinalAnnotationTag : AnnotationTagGeneric
10 10
    {
11
        // May cause problems?
12
        public FinalAnnotation Annotation { get; set; }
11 13
        public bool IsFinal { get; set; } = false;
12 14
        public List<User> Users { get; set; } = new();
13 15
    }
Backend/Core/GraphUtils/Intersections.cs
4 4
{
5 5
    public class Intersections
6 6
    {
7
        public static Dictionary<AnnotationTag, List<AnnotationTag>> FindIntersections(List<AnnotationTag> tags)
7
        public static Dictionary<AnnotationTagGeneric, List<AnnotationTagGeneric>> FindIntersections(List<AnnotationTagGeneric> tags)
8 8
        {
9
            var intersections = new Dictionary<AnnotationTag, List<AnnotationTag>>();
9
            var intersections = new Dictionary<AnnotationTagGeneric, List<AnnotationTagGeneric>>();
10 10

  
11 11
            for (int i = 0; i < tags.Count; i++)
12 12
            {
......
27 27
            return intersections;
28 28
        }
29 29

  
30
        public static Dictionary<AnnotationTag, int> ColorGraph(Dictionary<AnnotationTag, List<AnnotationTag>> source)
30
        public static Dictionary<AnnotationTagGeneric, int> ColorGraph(Dictionary<AnnotationTagGeneric, List<AnnotationTagGeneric>> source)
31 31
        {
32 32
            var res = ConvertToMatrix(source);
33 33
            var matrix = res.Item1;
......
47 47
                colors[coloredNode] = GetLowestUnusedColor(neighbourColors);
48 48
            }
49 49

  
50
            var coloring = new Dictionary<AnnotationTag, int>();
50
            var coloring = new Dictionary<AnnotationTagGeneric, int>();
51 51
            for (int i = 0; i < colors.Length; i++)
52 52
            {
53 53
                coloring[intToTagDict[i]] = colors[i];
......
104 104
            return neighbours;
105 105
        }
106 106

  
107
        private static (int[,], Dictionary<AnnotationTag, int>, Dictionary<int, AnnotationTag>) ConvertToMatrix(Dictionary<AnnotationTag, List<AnnotationTag>> source)
107
        private static (int[,], Dictionary<AnnotationTagGeneric, int>, Dictionary<int, AnnotationTagGeneric>) ConvertToMatrix(Dictionary<AnnotationTagGeneric, List<AnnotationTagGeneric>> source)
108 108
        {
109 109
            int[,] matrix = new int[source.Count, source.Count];
110 110
            for (int i = 0; i < source.Count; i++)
......
115 115
                }
116 116
            }
117 117

  
118
            var tagToIntDict = new Dictionary<AnnotationTag, int>();
119
            var intToTagDict = new Dictionary<int, AnnotationTag>();
118
            var tagToIntDict = new Dictionary<AnnotationTagGeneric, int>();
119
            var intToTagDict = new Dictionary<int, AnnotationTagGeneric>();
120 120

  
121 121
            int k = 0;
122 122
            foreach (var key in source.Keys)
Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
24 24
        private readonly DatabaseContext context;
25 25
        private readonly ILogger logger;
26 26
        private readonly IMapper mapper;
27
        private readonly IHTMLService htmlService;
27 28

  
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 29

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

  
39 38
        public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId)
......
145 144
                .ThenInclude(t => t.Category)
146 145
                .Include(at => at.SubTag)
147 146
                .OrderBy(at => at.Position)
147
                .Select(at => at as AnnotationTagGeneric)
148 148
                .ToList();
149 149

  
150 150
            List<TagInstanceInfo> tagInstanceInfos = new();
......
155 155
            }
156 156

  
157 157
            var docToRender = "";
158
            HTMLService.CachedInfo cachedInfoToReturn = new(); 
158 159
            if (annotation.CachedDocumentHTML == "")
159 160
            {
160
                docToRender = FullPreprocessHTML(documentContent.Content, tags);
161
                annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
162
                annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
163
                annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
164
                annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
165
                annotation.CachedCSS = JsonConvert.SerializeObject(TagInstanceCSS);
161
                var result = htmlService.FullPreprocessHTML(documentContent.Content, tags);
162
                cachedInfoToReturn = result.Item2;
163
                docToRender = result.Item1;
164

  
165
                annotation.CachedStartPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartPositions);
166
                annotation.CachedLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartLengths);
167
                annotation.CachedClosingPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingPositions);
168
                annotation.CachedClosingLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingLengths);
169
                annotation.CachedCSS = JsonConvert.SerializeObject(cachedInfoToReturn.TagInstanceCSS);
166 170
                annotation.ModifiedType = EModified.NONE;
167 171
                annotation.CachedDocumentHTML = docToRender;
168 172
                context.SaveChanges();
......
170 174
            else
171 175
            {
172 176
                docToRender = annotation.CachedDocumentHTML;
173
                TagStartPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedStartPositions);
174
                TagStartLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedLengths);
175
                TagClosingPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingPositions);
176
                TagClosingLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingLengths);
177
                //TagInstanceCSS = JsonConvert.DeserializeObject<List<TagInstanceCSSInfo>>(annotation.CachedCSS);
178

  
177
                HTMLService.CachedInfo cachedInfo = new()
178
                {
179
                    TagStartPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedStartPositions),
180
                    TagStartLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedLengths),
181
                    TagClosingPositions = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingPositions),
182
                    TagClosingLengths = JsonConvert.DeserializeObject<List<int>>(annotation.CachedClosingLengths)
183
                };
184
                
179 185
                // The annotation has been modified and we need to either add the new tag or remove the tag
180 186
                if (annotation.ModifiedType != EModified.NONE)
181 187
                {
182 188
                    if (annotation.ModifiedType == EModified.ADDED)
183 189
                    {
184 190
                        var lastModifiedTag = context.AnnotationTags.Where(at => at.Id == annotation.LastModifiedTagId).First();
185
                        docToRender = PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags);
191
                        var result = htmlService.PartialPreprocessHTMLAddTag(docToRender, documentContent.Content, lastModifiedTag, tags, cachedInfo);
192
                        docToRender = result.Item1;
193
                        cachedInfoToReturn = result.Item2;
186 194
                    }
187 195
                    else if (annotation.ModifiedType == EModified.REMOVED)
188 196
                    {
189
                        docToRender = PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags);
197
                        var result = htmlService.PartialPreprocessHTMLRemoveTag(docToRender, documentContent.Content, new AnnotationTag() { Id = annotation.LastModifiedTagId.Value }, tags, cachedInfo);
198
                        docToRender = result.Item1;
199
                        cachedInfoToReturn = result.Item2;
190 200
                    }
191 201

  
192 202
                    annotation.ModifiedType = EModified.NONE;
193
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(TagStartPositions);
194
                    annotation.CachedLengths = JsonConvert.SerializeObject(TagStartLengths);
195
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(TagClosingPositions);
196
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(TagClosingLengths);
203
                    annotation.CachedStartPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartPositions);
204
                    annotation.CachedLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagStartLengths);
205
                    annotation.CachedClosingPositions = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingPositions);
206
                    annotation.CachedClosingLengths = JsonConvert.SerializeObject(cachedInfoToReturn.TagClosingLengths);
197 207
                    annotation.CachedDocumentHTML = docToRender;
198
                    annotation.CachedCSS = JsonConvert.SerializeObject(TagInstanceCSS);
208
                    annotation.CachedCSS = JsonConvert.SerializeObject(cachedInfoToReturn.TagInstanceCSS);
199 209
                    context.SaveChanges();
200 210
                }
201 211
            }
......
205 215
            {
206 216
                SourceDocumentContent = documentContent.Content,
207 217
                DocumentToRender = docToRender,
208
                TagStartPositions = TagStartPositions.ToArray(),
209
                TagLengths = TagStartLengths.ToArray(),
218
                TagStartPositions = cachedInfoToReturn.TagStartPositions.ToArray(),
219
                TagLengths = cachedInfoToReturn.TagStartLengths.ToArray(),
210 220
                Note = annotation.Note,
211 221
                State = annotation.State,
212 222
                Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT,
213 223
                TagInstances = tagInstanceInfos,
214
                CSSInfo = TagInstanceCSS
224
                CSSInfo = cachedInfoToReturn.TagInstanceCSS
215 225
            };
216 226

  
217
            NodeDict.Clear();
218

  
219 227
            return annotationInfo;
220 228
        }
221 229

  
222
        private List<int> TagStartPositions = new();
223
        private List<int> TagStartLengths = new();
224
        private List<int> TagClosingPositions = new();
225
        private List<int> TagClosingLengths = new();
226
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
227
        private List<TagInstanceCSSInfo> TagInstanceCSS = new();
228

  
229
        /*
230
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
231
         */
232

  
233

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

  
241
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
242
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
243

  
244
            int currentId = 0;
245

  
246
            FillNodeDict(descendantsOriginal, descendantsToEdit);
247
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
248

  
249
            WrapTextInSpan(descendantsOriginal, docToEdit);
250
            int descCount = descendantsToEdit.Count;
251

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

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

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

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

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

  
299
            }
300

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

  
326
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTag tag)
327
        {
328
            // full fill
329
            string textSelected = node.InnerText;
330

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

  
335
            EPosition markerPosition = EPosition.MARK_NONE;
336
            if (selectionEnd == end && selectionStart == start)
337
            {
338
                markerPosition = EPosition.MARK_LEFT_RIGHT;
339
            }
340

  
341
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
342
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
343

  
344
            return spanSelected;
345
        }
346

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

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

  
359
            int spanSelectedStart = start;
360
            int spanAfterStart = start + textSelected.Length;
361

  
362
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
363
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
364

  
365
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
366
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
367

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

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

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

  
383
            int spanBeforeStart = start;
384
            int spanSelectedStart = start + textBefore.Length;
385

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

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

  
392
            return new() { spanSelected, spanBefore };
393
        }
394

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

  
404
            var parentNode = node.ParentNode;
405
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
406
            parentNode.ChildNodes.RemoveAt(nodeIndex);
407

  
408
            int spanBeforeStart = start;
409
            int spanSelectedStart = start + textBefore.Length;
410
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
411

  
412
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
413
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
414

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

  
418
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
419
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
420

  
421
            return new() { spanSelected, spanBefore, spanAfter };
422
        }
423

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

  
434
            if (instanceId != null)
435
            {
436
                span.AddClass("annotation");
437
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
438
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
439

  
440
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
441
                {
442
                    span.Attributes.Add("start", "1");
443
                }
444
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
445
                {
446
                    span.Attributes.Add("end", "1");
447
                }
448
            }
449

  
450
            return span;
451
        }
452

  
453
        private enum EPosition
454
        {
455
            MARK_LEFT = 5,
456
            MARK_RIGHT = 3,
457
            MARK_LEFT_RIGHT = 2,
458
            MARK_NONE = 0
459
        }
460

  
461
        private void ModifyLinks(IEnumerable<HtmlNode> descendantsOriginal)
462
        {
463
            foreach (var descendant in descendantsOriginal)
464
            {
465
                if (descendant.Name == "a")
466
                {
467
                    if (descendant.Attributes.Contains("href"))
468
                    {
469
                        descendant.SetAttributeValue("href", "/link?url=" + descendant.Attributes["href"].Value);
470
                        descendant.SetAttributeValue("target", "_blank");
471
                    }
472
                }
473
            }
474
        }
475

  
476
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
477
        {
478
            // Special case for non-html documents
479
            if (descendantsOriginal.Count() == 2)
480
            {
481
                var documentNode = descendantsOriginal.ElementAt(0);
482
                var childNode = descendantsOriginal.ElementAt(1);
483
                if (documentNode.Name == "#document" && childNode.Name == "#text")
484
                {
485
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
486
                    coveringSpan.InnerHtml = childNode.InnerHtml;
487
                    TagStartPositions.Add(childNode.InnerStartIndex);
488
                    TagStartLengths.Add(0);
489
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
490
                    TagClosingLengths.Add(0);
491
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
492

  
493
                    var parent = NodeDict[documentNode];
494

  
495
                    parent.ChildNodes.RemoveAt(0);
496
                    parent.ChildNodes.Add(coveringSpan);
497

  
498
                    return;
499
                }
500
            }
501

  
502
            foreach (var node in descendantsOriginal)
503
            {
504
                var originalNode = node;
505
                var toEditNode = NodeDict[node];
506

  
507
                if (originalNode.Name.Contains("#"))
508
                {
509
                    continue;
510
                }
511
                else
512
                {
513
                    bool onlyText = true;
514
                    bool onlySubtags = true;
515

  
516
                    foreach (var child in node.ChildNodes)
517
                    {
518
                        if (child.Name.Contains("#"))
519
                        {
520
                            onlySubtags = false;
521
                        }
522
                        else
523
                        {
524
                            onlyText = false;
525
                        }
526
                    }
527

  
528
                    if (onlyText || onlySubtags)
529
                    {
530
                        continue;
531
                    }
532
                    else
533
                    {
534

  
535
                        foreach (var child in node.ChildNodes)
536
                        {
537
                            if (child.Name.Contains("#text"))
538
                            {
539
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
540
                                coveringSpan.InnerHtml = child.InnerHtml;
541
                                TagStartPositions.Add(child.InnerStartIndex);
542
                                TagStartLengths.Add(0);
543
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
544
                                TagClosingLengths.Add(0);
545
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
546

  
547
                                var parent = NodeDict[node];
548
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
549

  
550
                                parent.ChildNodes.RemoveAt(index);
551
                                parent.ChildNodes.Insert(index, coveringSpan);
552
                            }
553
                        }
554
                    }
555
                }
556
            }
557
        }
558

  
559
        private void GenerateCSS(List<AnnotationTag> tags)
560
        {
561
            /*string inner = "span.annotation {border-bottom: 2px solid;}";
562
            inner += "span {line-height: 30px}\n";*/
563

  
564
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
565
            foreach (var tag in tags.DistinctBy(t => t.Instance))
566
            {
567
                var padding = (tagPaddingDict[tag] + 1) * 2;
568
                TagInstanceCSS.Add(new()
569
                {
570
                    InstanceId = tag.Instance,
571
                    Color = tag.Tag.Color,
572
                    Padding = padding
573
                });
574
            }
575
        }
576

  
577
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
578
        {
579
            foreach (var node in descendantsOriginal)
580
            {
581
                var originalNode = node;
582
                var toEditNode = NodeDict[node];
583

  
584
                if (originalNode.Name.Contains("#"))
585
                {
586
                    continue;
587
                }
588
                else
589
                {
590
                    TagStartPositions.Add(originalNode.OuterStartIndex);
591
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
592
                    currentId = TagStartPositions.Count - 1;
593
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
594

  
595
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
596
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
597
                }
598
            }
599
        }
600

  
601
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
602
        {
603
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
604
            {
605
                Original = orig,
606
                ToEdit = toEdit
607
            });
608
            foreach (var node in zipped)
609
            {
610
                var originalNode = node.Original;
611
                var toEditNode = node.ToEdit;
612
                NodeDict.Add(originalNode, toEditNode);
613
            }
614
        }
615

  
616

  
617
        /*
618
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
619
         */
620

  
621
        /*
622
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
623
         */
624

  
625
        private string PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTag> tags)
626
        {
627
            var docOriginal = new HtmlDocument();
628
            docOriginal.LoadHtml(htmlOriginal);
629
            var docToEdit = new HtmlDocument();
630
            docToEdit.LoadHtml(htmlToEdit);
631

  
632
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
633

  
634
            int i = 0;
635
            List<HtmlNode> addedForSelection = new();
636

  
637
            int descendantsCount = descendantsToEdit.Count();
638
            while (i < descendantsCount)
639
            {
640
                for (; i < descendantsCount; i++)
641
                {
642
                    var node = descendantsToEdit.ElementAt(i);
643
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
644
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
645
                    {
646
                        continue;
647
                    }
648

  
649
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
650

  
651
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
652
                    var end = TagClosingPositions[nodeId];
653

  
654
                    int selectionStart = tagToAdd.Position;
655
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
656

  
657
                    if (selectionStart < end && selectionEnd > start)
658
                    {
659
                        if (selectionStart <= start && selectionEnd >= end)
660
                        {
661
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
662
                        }
663
                        else if (selectionStart <= start)
664
                        {
665
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
666
                        }
667
                        else if (selectionEnd >= end)
668
                        {
669
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
670
                        }
671
                        else
672
                        {
673
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
674
                        }
675
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
676
                        descendantsCount = descendantsToEdit.Count();
677
                        break;
678
                    }
679
                }
680
            }
681

  
682
            GenerateCSS(tags);
683
            return docToEdit.DocumentNode.OuterHtml;
684
        }
685

  
686

  
687
        private string PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTag> tags)
688
        {
689
            var docOriginal = new HtmlDocument();
690
            docOriginal.LoadHtml(htmlOriginal);
691
            var docToEdit = new HtmlDocument();
692
            docToEdit.LoadHtml(htmlToEdit);
693

  
694
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
695

  
696
            int i = 0;
697
            int descendantsCount = descendantsToEdit.Count();
698
            while (i < descendantsCount)
699
            {
700
                for (; i < descendantsCount; i++)
701
                {
702
                    var node = descendantsToEdit.ElementAt(i);
703
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
704
                    {
705
                        continue;
706
                    }
707
                    else
708
                    {
709
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
710
                        {
711
                            continue;
712
                        }
713

  
714
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
715
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
716
                        node.Attributes.Remove("class");
717
                        node.Attributes.Remove("start");
718
                        node.Attributes.Remove("end");
719

  
720
                        /*var parent = node.ParentNode;
721
                        var contents = node.ChildNodes;
722
                        int index = parent.ChildNodes.IndexOf(node);
723
                        parent.ChildNodes.RemoveAt(index);
724

  
725
                        List<HtmlNode> newChildren = new();
726
                        for (int j = 0; j < index; j++)
727
                        {
728
                            newChildren.Add(parent.ChildNodes[j]);
729
                        }
730
                        for (int j = 0; j < contents.Count; j++)
731
                        {
732
                            newChildren.Add(contents[j]);
733
                        }
734
                        for (int j = index; j < parent.ChildNodes.Count; j++)
735
                        {
736
                            newChildren.Add(parent.ChildNodes[j]);
737
                        }
738

  
739
                        parent.ChildNodes.Clear();
740
                        foreach (var child in newChildren) { parent.ChildNodes.Add(child); }*/
741

  
742
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
743
                        descendantsCount = descendantsToEdit.Count();
744
                        break;
745
                    }
746
                }
747
            }
748

  
749
            GenerateCSS(tags);
750
            return docToEdit.DocumentNode.OuterHtml;
751
        }
752

  
753
        /*
754
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
755
         */
756

  
757

  
758 230
        // TODO temporary
759 231
        private bool IsHtml(string text)
760 232
        {
Backend/Core/Services/HTMLService/HTMLService.cs
1
using Core.Entities;
2
using Core.GraphUtils;
3
using Ganss.XSS;
4
using HtmlAgilityPack;
5
using Models.Tags;
6
using System;
7
using System.Collections.Generic;
8
using System.Linq;
9
using System.Text;
10
using System.Threading.Tasks;
11

  
12
namespace Core.Services
13
{
14
    public class HTMLService : IHTMLService
15
    {
16

  
17
        private const string TAG_ID_ATTRIBUTE_NAME = "aswi-tag-id";
18
        private const string TAG_INSTANCE_ATTRIBUTE_NAME = "aswi-tag-instance";
19
        private const string TAG_EF_ID_ATTRIBUTE_NAME = "aswi-tag-ef-id";
20

  
21
        public class CachedInfo
22
        {
23
            public List<int> TagStartPositions = new();
24
            public List<int> TagStartLengths = new();
25
            public List<int> TagClosingPositions = new();
26
            public List<int> TagClosingLengths = new();
27
            public Dictionary<HtmlNode, HtmlNode> NodeDict = new();
28
            public List<TagInstanceCSSInfo> TagInstanceCSS = new();
29
        }
30

  
31
        private List<int> TagStartPositions = new();
32
        private List<int> TagStartLengths = new();
33
        private List<int> TagClosingPositions = new();
34
        private List<int> TagClosingLengths = new();
35
        private Dictionary<HtmlNode, HtmlNode> NodeDict = new();
36
        private List<TagInstanceCSSInfo> TagInstanceCSS = new();
37

  
38
        private void UnpackCachedInfo(CachedInfo cachedInfo)
39
        {
40
            TagStartPositions = cachedInfo.TagStartPositions;
41
            TagStartLengths = cachedInfo.TagStartLengths;
42
            TagClosingPositions = cachedInfo.TagClosingPositions;
43
            TagClosingLengths = cachedInfo.TagClosingLengths;
44
            NodeDict = cachedInfo.NodeDict;
45
            TagInstanceCSS = cachedInfo.TagInstanceCSS;
46
        }
47

  
48

  
49
        /*
50
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
51
         */
52

  
53
        public (string, CachedInfo) FullPreprocessHTML(string htmlSource, List<AnnotationTagGeneric> tags)
54
        {
55
            var docOriginal = new HtmlDocument();
56
            docOriginal.LoadHtml(htmlSource);
57
            var docToEdit = new HtmlDocument();
58
            docToEdit.LoadHtml(htmlSource);
59

  
60
            var descendantsOriginal = docOriginal.DocumentNode.DescendantsAndSelf();
61
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
62

  
63
            int currentId = 0;
64

  
65
            FillNodeDict(descendantsOriginal, descendantsToEdit);
66
            AssignIdsToOriginalDocument(descendantsOriginal, ref currentId);
67

  
68
            WrapTextInSpan(descendantsOriginal, docToEdit);
69
            int descCount = descendantsToEdit.Count;
70

  
71
            foreach (var tag in tags)
72
            {
73
                int i = 0;
74
                List<HtmlNode> addedForSelection = new();
75
                while (i < descCount)
76
                {
77
                    for (; i < descCount; i++)
78
                    {
79
                        var node = descendantsToEdit.ElementAt(i);
80
                        if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) || node.ParentNode.Name == "style")
81
                        {
82
                            continue;
83
                        }
84

  
85
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
86

  
87
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
88
                        var end = TagClosingPositions[nodeId];
89

  
90
                        int selectionStart = tag.Position;
91
                        int selectionEnd = tag.Position + tag.Length;
92

  
93
                        if (selectionStart < end && selectionEnd > start)
94
                        {
95
                            if (selectionStart <= start && selectionEnd >= end)
96
                            {
97
                                addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
98
                            }
99
                            else if (selectionStart <= start)
100
                            {
101
                                addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
102
                            }
103
                            else if (selectionEnd >= end)
104
                            {
105
                                addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
106
                            }
107
                            else
108
                            {
109
                                addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tag));
110
                            }
111
                            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
112
                            descCount = descendantsToEdit.Count;
113
                            break;
114
                        }
115
                    }
116
                }
117

  
118
            }
119

  
120
            ModifyLinks(descendantsToEdit);
121
            string docToRender = docToEdit.DocumentNode.OuterHtml;
122
            HtmlSanitizer sanitizer = new HtmlSanitizer();
123
            sanitizer.AllowedAttributes.Clear();
124
            sanitizer.AllowedAttributes.Add(TAG_ID_ATTRIBUTE_NAME);
125
            sanitizer.AllowedAttributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME);
126
            sanitizer.AllowedAttributes.Add("href");
127
            sanitizer.AllowedAttributes.Add("end");
128
            sanitizer.AllowedAttributes.Add("start");
129
            sanitizer.AllowedAttributes.Add("class");
130
            sanitizer.AllowedAttributes.Add("target");
131
            if (sanitizer.AllowedTags.Contains("script"))
132
            {
133
                sanitizer.AllowedTags.Remove("script");
134
            }
135
            if (!sanitizer.AllowedTags.Contains("style"))
136
            {
137
                sanitizer.AllowedTags.Add("style");
138
            }
139
            sanitizer.AllowedTags.Add("a");
140
            docToRender = sanitizer.Sanitize(docToRender);
141
            GenerateCSS(tags);
142

  
143
            return (docToRender, new CachedInfo()
144
            {
145
                TagStartPositions = TagStartPositions,
146
                TagClosingPositions = TagClosingPositions,
147
                TagStartLengths = TagStartLengths,
148
                TagClosingLengths = TagClosingLengths,
149
                TagInstanceCSS = TagInstanceCSS,
150
                NodeDict = NodeDict
151
            });
152
        }
153

  
154
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTagGeneric tag)
155
        {
156
            // full fill
157
            string textSelected = node.InnerText;
158

  
159
            var parentNode = node.ParentNode;
160
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
161
            parentNode.ChildNodes.RemoveAt(nodeIndex);
162

  
163
            EPosition markerPosition = EPosition.MARK_NONE;
164
            if (selectionEnd == end && selectionStart == start)
165
            {
166
                markerPosition = EPosition.MARK_LEFT_RIGHT;
167
            }
168

  
169
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
170
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
171

  
172
            return spanSelected;
173
        }
174

  
175
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
176
                                             AnnotationTagGeneric tag)
177
        {
178
            // partial fill, end gap
179
            string text = node.InnerText;
180
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
181
            string textSelected = text.Substring(0, selectionEnd - start);
182

  
183
            var parentNode = node.ParentNode;
184
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
185
            parentNode.ChildNodes.RemoveAt(nodeIndex);
186

  
187
            int spanSelectedStart = start;
188
            int spanAfterStart = start + textSelected.Length;
189

  
190
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
191
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
192

  
193
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
194
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
195

  
196
            return new() { spanSelected, spanAfter };
197
        }
198

  
199
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
200
                                             AnnotationTagGeneric tag)
201
        {
202
            // partial fill, start gap
203
            string text = node.InnerText;
204
            string textBefore = text.Substring(0, selectionStart - start);
205
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
206

  
207
            var parentNode = node.ParentNode;
208
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
209
            parentNode.ChildNodes.RemoveAt(nodeIndex);
210

  
211
            int spanBeforeStart = start;
212
            int spanSelectedStart = start + textBefore.Length;
213

  
214
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
215
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
216

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

  
220
            return new() { spanSelected, spanBefore };
221
        }
222

  
223
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
224
                                                 AnnotationTagGeneric tag)
225
        {
226
            // partial fill, start gap end gap
227
            string text = node.InnerText;
228
            string textBefore = text.Substring(0, selectionStart - start);
229
            string textAfter = text.Substring(selectionStart - start + tag.Length);
230
            string textSelected = text.Substring(selectionStart - start, tag.Length);
231

  
232
            var parentNode = node.ParentNode;
233
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
234
            parentNode.ChildNodes.RemoveAt(nodeIndex);
235

  
236
            int spanBeforeStart = start;
237
            int spanSelectedStart = start + textBefore.Length;
238
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
239

  
240
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
241
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
242

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

  
246
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
247
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
248

  
249
            return new() { spanSelected, spanBefore, spanAfter };
250
        }
251

  
252
        private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE)
253
        {
254
            HtmlNode span = doc.CreateElement("span");
255
            span.InnerHtml = text;
256
            TagStartPositions.Add(startPosition);
257
            TagStartLengths.Add(0);
258
            TagClosingPositions.Add(startPosition + text.Length);
259
            TagClosingLengths.Add(0);
260
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
261

  
262
            if (instanceId != null)
263
            {
264
                span.AddClass("annotation");
265
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
266
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
267

  
268
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
269
                {
270
                    span.Attributes.Add("start", "1");
271
                }
272
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
273
                {
274
                    span.Attributes.Add("end", "1");
275
                }
276
            }
277

  
278
            return span;
279
        }
280

  
281
        private enum EPosition
282
        {
283
            MARK_LEFT = 5,
284
            MARK_RIGHT = 3,
285
            MARK_LEFT_RIGHT = 2,
286
            MARK_NONE = 0
287
        }
288

  
289
        private void ModifyLinks(IEnumerable<HtmlNode> descendantsOriginal)
290
        {
291
            foreach (var descendant in descendantsOriginal)
292
            {
293
                if (descendant.Name == "a")
294
                {
295
                    if (descendant.Attributes.Contains("href"))
296
                    {
297
                        descendant.SetAttributeValue("href", "/link?url=" + descendant.Attributes["href"].Value);
298
                        descendant.SetAttributeValue("target", "_blank");
299
                    }
300
                }
301
            }
302
        }
303

  
304
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
305
        {
306
            // Special case for non-html documents
307
            if (descendantsOriginal.Count() == 2)
308
            {
309
                var documentNode = descendantsOriginal.ElementAt(0);
310
                var childNode = descendantsOriginal.ElementAt(1);
311
                if (documentNode.Name == "#document" && childNode.Name == "#text")
312
                {
313
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
314
                    coveringSpan.InnerHtml = childNode.InnerHtml;
315
                    TagStartPositions.Add(childNode.InnerStartIndex);
316
                    TagStartLengths.Add(0);
317
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
318
                    TagClosingLengths.Add(0);
319
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
320

  
321
                    var parent = NodeDict[documentNode];
322

  
323
                    parent.ChildNodes.RemoveAt(0);
324
                    parent.ChildNodes.Add(coveringSpan);
325

  
326
                    return;
327
                }
328
            }
329

  
330
            foreach (var node in descendantsOriginal)
331
            {
332
                var originalNode = node;
333
                var toEditNode = NodeDict[node];
334

  
335
                if (originalNode.Name.Contains("#"))
336
                {
337
                    continue;
338
                }
339
                else
340
                {
341
                    bool onlyText = true;
342
                    bool onlySubtags = true;
343

  
344
                    foreach (var child in node.ChildNodes)
345
                    {
346
                        if (child.Name.Contains("#"))
347
                        {
348
                            onlySubtags = false;
349
                        }
350
                        else
351
                        {
352
                            onlyText = false;
353
                        }
354
                    }
355

  
356
                    if (onlyText || onlySubtags)
357
                    {
358
                        continue;
359
                    }
360
                    else
361
                    {
362

  
363
                        foreach (var child in node.ChildNodes)
364
                        {
365
                            if (child.Name.Contains("#text"))
366
                            {
367
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
368
                                coveringSpan.InnerHtml = child.InnerHtml;
369
                                TagStartPositions.Add(child.InnerStartIndex);
370
                                TagStartLengths.Add(0);
371
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
372
                                TagClosingLengths.Add(0);
373
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
374

  
375
                                var parent = NodeDict[node];
376
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
377

  
378
                                parent.ChildNodes.RemoveAt(index);
379
                                parent.ChildNodes.Insert(index, coveringSpan);
380
                            }
381
                        }
382
                    }
383
                }
384
            }
385
        }
386

  
387
        private void GenerateCSS(List<AnnotationTagGeneric> tags)
388
        {
389
            /*string inner = "span.annotation {border-bottom: 2px solid;}";
390
            inner += "span {line-height: 30px}\n";*/
391

  
392
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
393
            foreach (var tag in tags.DistinctBy(t => t.Instance))
394
            {
395
                var padding = (tagPaddingDict[tag] + 1) * 2;
396
                TagInstanceCSS.Add(new()
397
                {
398
                    InstanceId = tag.Instance,
399
                    Color = tag.Tag.Color,
400
                    Padding = padding
401
                });
402
            }
403
        }
404

  
405
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
406
        {
407
            foreach (var node in descendantsOriginal)
408
            {
409
                var originalNode = node;
410
                var toEditNode = NodeDict[node];
411

  
412
                if (originalNode.Name.Contains("#"))
413
                {
414
                    continue;
415
                }
416
                else
417
                {
418
                    TagStartPositions.Add(originalNode.OuterStartIndex);
419
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
420
                    currentId = TagStartPositions.Count - 1;
421
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
422

  
423
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
424
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
425
                }
426
            }
427
        }
428

  
429
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
430
        {
431
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
432
            {
433
                Original = orig,
434
                ToEdit = toEdit
435
            });
436
            foreach (var node in zipped)
437
            {
438
                var originalNode = node.Original;
439
                var toEditNode = node.ToEdit;
440
                NodeDict.Add(originalNode, toEditNode);
441
            }
442
        }
443

  
444

  
445
        /*
446
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
447
         */
448

  
449
        /*
450
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
451
         */
452

  
453
        public (string, CachedInfo) PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
454
        {
455
            UnpackCachedInfo(cachedInfo);
456

  
457
            var docOriginal = new HtmlDocument();
458
            docOriginal.LoadHtml(htmlOriginal);
459
            var docToEdit = new HtmlDocument();
460
            docToEdit.LoadHtml(htmlToEdit);
461

  
462
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
463

  
464
            int i = 0;
465
            List<HtmlNode> addedForSelection = new();
466

  
467
            int descendantsCount = descendantsToEdit.Count();
468
            while (i < descendantsCount)
469
            {
470
                for (; i < descendantsCount; i++)
471
                {
472
                    var node = descendantsToEdit.ElementAt(i);
473
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
474
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
475
                    {
476
                        continue;
477
                    }
478

  
479
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
480

  
481
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
482
                    var end = TagClosingPositions[nodeId];
483

  
484
                    int selectionStart = tagToAdd.Position;
485
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
486

  
487
                    if (selectionStart < end && selectionEnd > start)
488
                    {
489
                        if (selectionStart <= start && selectionEnd >= end)
490
                        {
491
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
492
                        }
493
                        else if (selectionStart <= start)
494
                        {
495
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
496
                        }
497
                        else if (selectionEnd >= end)
498
                        {
499
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
500
                        }
501
                        else
502
                        {
503
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
504
                        }
505
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
506
                        descendantsCount = descendantsToEdit.Count();
507
                        break;
508
                    }
509
                }
510
            }
511

  
512
            GenerateCSS(tags);
513
            return (docToEdit.DocumentNode.OuterHtml, new()
514
            {
515
                TagStartPositions = TagStartPositions,
516
                TagStartLengths = TagStartLengths,
517
                TagClosingPositions = TagClosingPositions,
518
                TagClosingLengths = TagClosingLengths,
519
                TagInstanceCSS = TagInstanceCSS,
520
                NodeDict = NodeDict
521
            });
522
        }
523

  
524

  
525
        public (string, CachedInfo) PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
526
        {
527
            UnpackCachedInfo(cachedInfo);
528

  
529
            var docOriginal = new HtmlDocument();
530
            docOriginal.LoadHtml(htmlOriginal);
531
            var docToEdit = new HtmlDocument();
532
            docToEdit.LoadHtml(htmlToEdit);
533

  
534
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
535

  
536
            int i = 0;
537
            int descendantsCount = descendantsToEdit.Count();
538
            while (i < descendantsCount)
539
            {
540
                for (; i < descendantsCount; i++)
541
                {
542
                    var node = descendantsToEdit.ElementAt(i);
543
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
544
                    {
545
                        continue;
546
                    }
547
                    else
548
                    {
549
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
550
                        {
551
                            continue;
552
                        }
553

  
554
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
555
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
556
                        node.Attributes.Remove("class");
557
                        node.Attributes.Remove("start");
558
                        node.Attributes.Remove("end");
559

  
560
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
561
                        descendantsCount = descendantsToEdit.Count();
562
                        break;
563
                    }
564
                }
565
            }
566

  
567
            GenerateCSS(tags);
568
            return (docToEdit.DocumentNode.OuterHtml, new()
569
            {
570
                TagStartPositions = TagStartPositions,
571
                TagStartLengths = TagStartLengths,
572
                TagClosingPositions = TagClosingPositions,
573
                TagClosingLengths = TagClosingLengths,
574
                TagInstanceCSS = TagInstanceCSS,
575
                NodeDict = NodeDict
576
            });
577
        }
578

  
579
        /*
580
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
581
         */
582
    }
583
}
Backend/Core/Services/HTMLService/IHTMLService.cs
1
using Core.Entities;
2
using System;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Text;
6
using System.Threading.Tasks;
7
using static Core.Services.HTMLService;
8

  
9
namespace Core.Services
10
{
11
    public interface IHTMLService
12
    {
13
        public (string, CachedInfo) FullPreprocessHTML(string htmlSource, List<AnnotationTagGeneric> tags);
14
        public (string, CachedInfo) PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToAdd, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo);
15
        public (string, CachedInfo) PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTag tagToRemove, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo);
16
    }
17
}
Backend/Core/Services/Registration.cs
16 16
            builder.Services.AddScoped<IAuthService, AuthService>();
17 17
            builder.Services.AddScoped<ITagService, TagServiceEF>();
18 18
            builder.Services.AddScoped<IAnnotationService, AnnotationServiceEF>();
19
            
19
            builder.Services.AddScoped<IHTMLService, HTMLService>();
20

  
20 21
            builder.Services.AddScoped<IJwtUtils, JwtUtils>();
21 22
        }
22 23
    }

Také k dispozici: Unified diff