Projekt

Obecné

Profil

Stáhnout (25.3 KB) Statistiky
| Větev: | Tag: | Revize:
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

    
70
            descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
71
            int descCount = descendantsToEdit.Count;
72

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

    
87
                        int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
88

    
89
                        var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
90
                        var end = TagClosingPositions[nodeId];
91

    
92
                        int selectionStart = tag.Position;
93
                        int selectionEnd = tag.Position + tag.Length;
94

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

    
120
            }
121

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

    
148
            return (docToRender, new CachedInfo()
149
            {
150
                TagStartPositions = TagStartPositions,
151
                TagClosingPositions = TagClosingPositions,
152
                TagStartLengths = TagStartLengths,
153
                TagClosingLengths = TagClosingLengths,
154
                TagInstanceCSS = TagInstanceCSS,
155
                NodeDict = NodeDict
156
            });
157
        }
158

    
159
        private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTagGeneric tag)
160
        {
161
            // full fill
162
            string textSelected = node.InnerText;
163

    
164
            var parentNode = node.ParentNode;
165
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
166
            parentNode.ChildNodes.RemoveAt(nodeIndex);
167

    
168
            EPosition markerPosition = EPosition.MARK_NONE;
169
            if (selectionEnd == end && selectionStart == start)
170
            {
171
                markerPosition = EPosition.MARK_LEFT_RIGHT;
172
            }
173
            else if (selectionEnd == end)
174
            {
175
                markerPosition = EPosition.MARK_RIGHT;
176
            }
177
            else if (selectionStart == start)
178
            {
179
                markerPosition = EPosition.MARK_LEFT;
180
            }
181

    
182
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
183
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
184

    
185
            return spanSelected;
186
        }
187

    
188
        private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
189
                                             AnnotationTagGeneric tag)
190
        {
191
            // partial fill, end gap
192
            string text = node.InnerText;
193
            string textAfter = text.Substring(Math.Min(selectionStart - start + tag.Length, text.Length));
194
            string textSelected = text.Substring(0, selectionEnd - start);
195

    
196
            var parentNode = node.ParentNode;
197
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
198
            parentNode.ChildNodes.RemoveAt(nodeIndex);
199

    
200
            int spanSelectedStart = start;
201
            int spanAfterStart = start + textSelected.Length;
202

    
203
            var position = EPosition.MARK_RIGHT;
204
            if (selectionStart == start)
205
            {
206
                position = EPosition.MARK_LEFT_RIGHT;
207
            }
208

    
209
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, position);
210
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
211

    
212
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
213
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
214

    
215
            return new() { spanSelected, spanAfter };
216
        }
217

    
218
        private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
219
                                             AnnotationTagGeneric tag)
220
        {
221
            // partial fill, start gap
222
            string text = node.InnerText;
223
            string textBefore = text.Substring(0, selectionStart - start);
224
            string textSelected = text.Substring(selectionStart - start, Math.Min(tag.Length, text.Length - textBefore.Length));
225

    
226
            var parentNode = node.ParentNode;
227
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
228
            parentNode.ChildNodes.RemoveAt(nodeIndex);
229

    
230
            int spanBeforeStart = start;
231
            int spanSelectedStart = start + textBefore.Length;
232

    
233
            var position = EPosition.MARK_LEFT;
234
            if (selectionEnd == end)
235
            {
236
                position = EPosition.MARK_LEFT_RIGHT;
237
            }
238

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

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

    
245
            return new() { spanSelected, spanBefore };
246
        }
247

    
248
        private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit,
249
                                                 AnnotationTagGeneric tag)
250
        {
251
            // partial fill, start gap end gap
252
            string text = node.InnerText;
253
            string textBefore = text.Substring(0, selectionStart - start);
254
            string textAfter = text.Substring(selectionStart - start + tag.Length);
255
            string textSelected = text.Substring(selectionStart - start, tag.Length);
256

    
257
            var parentNode = node.ParentNode;
258
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
259
            parentNode.ChildNodes.RemoveAt(nodeIndex);
260

    
261
            int spanBeforeStart = start;
262
            int spanSelectedStart = start + textBefore.Length;
263
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
264

    
265
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
266
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
267

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

    
271
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
272
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
273

    
274
            return new() { spanSelected, spanBefore, spanAfter };
275
        }
276

    
277
        private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE)
278
        {
279
            HtmlNode span = doc.CreateElement("span");
280
            span.InnerHtml = text;
281
            TagStartPositions.Add(startPosition);
282
            TagStartLengths.Add(0);
283
            TagClosingPositions.Add(startPosition + text.Length);
284
            TagClosingLengths.Add(0);
285
            span.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, tagId.ToString());
286

    
287
            if (instanceId != null)
288
            {
289
                span.AddClass("annotation");
290
                span.Attributes.Add(TAG_INSTANCE_ATTRIBUTE_NAME, instanceId.Value.ToString());
291
                span.Attributes.Add(TAG_EF_ID_ATTRIBUTE_NAME, entityId.ToString());
292
                span.Attributes.Add("id", entityId.ToString());
293

    
294
                if (position == EPosition.MARK_LEFT || position == EPosition.MARK_LEFT_RIGHT)
295
                {
296
                    span.Attributes.Add("start", "1");
297
                }
298
                if (position == EPosition.MARK_RIGHT || position == EPosition.MARK_LEFT_RIGHT)
299
                {
300
                    span.Attributes.Add("end", "1");
301
                }
302
            }
303

    
304
            return span;
305
        }
306

    
307
        private enum EPosition
308
        {
309
            MARK_LEFT = 5,
310
            MARK_RIGHT = 3,
311
            MARK_LEFT_RIGHT = 2,
312
            MARK_NONE = 0
313
        }
314

    
315
        private void ModifyLinks(IEnumerable<HtmlNode> descendantsOriginal)
316
        {
317
            foreach (var descendant in descendantsOriginal)
318
            {
319
                if (descendant.Name == "a")
320
                {
321
                    if (descendant.Attributes.Contains("href"))
322
                    {
323
                        descendant.SetAttributeValue("href", "/link?url=" + descendant.Attributes["href"].Value);
324
                        descendant.SetAttributeValue("target", "_blank");
325
                    }
326
                }
327
            }
328
        }
329

    
330
        private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit)
331
        {
332
            // Special case for non-html documents
333
            if (descendantsOriginal.Count() == 2)
334
            {
335
                var documentNode = descendantsOriginal.ElementAt(0);
336
                var childNode = descendantsOriginal.ElementAt(1);
337
                if (documentNode.Name == "#document" && childNode.Name == "#text")
338
                {
339
                    HtmlNode coveringSpan = docToEdit.CreateElement("span");
340
                    coveringSpan.InnerHtml = childNode.InnerHtml;
341
                    TagStartPositions.Add(childNode.InnerStartIndex);
342
                    TagStartLengths.Add(0);
343
                    TagClosingPositions.Add(childNode.InnerStartIndex + childNode.InnerLength);
344
                    TagClosingLengths.Add(0);
345
                    coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
346

    
347
                    var parent = NodeDict[documentNode];
348

    
349
                    parent.ChildNodes.RemoveAt(0);
350
                    parent.ChildNodes.Add(coveringSpan);
351

    
352
                    return;
353
                }
354
            }
355

    
356
            foreach (var node in descendantsOriginal)
357
            {
358
                var originalNode = node;
359
                var toEditNode = NodeDict[node];
360

    
361
                if (originalNode.Name.Contains("#"))
362
                {
363
                    continue;
364
                }
365
                else
366
                {
367
                    bool onlyText = true;
368
                    bool onlySubtags = true;
369

    
370
                    foreach (var child in node.ChildNodes)
371
                    {
372
                        if (child.Name.Contains("#"))
373
                        {
374
                            onlySubtags = false;
375
                        }
376
                        else
377
                        {
378
                            onlyText = false;
379
                        }
380
                    }
381

    
382
                    if (onlyText || onlySubtags)
383
                    {
384
                        continue;
385
                    }
386
                    else
387
                    {
388

    
389
                        foreach (var child in node.ChildNodes)
390
                        {
391
                            if (child.Name.Contains("#text"))
392
                            {
393
                                HtmlNode coveringSpan = docToEdit.CreateElement("span");
394
                                coveringSpan.InnerHtml = child.InnerHtml;
395
                                TagStartPositions.Add(child.InnerStartIndex);
396
                                TagStartLengths.Add(0);
397
                                TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength);
398
                                TagClosingLengths.Add(0);
399
                                coveringSpan.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, (TagStartPositions.Count - 1).ToString());
400

    
401
                                var parent = NodeDict[node];
402
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
403

    
404
                                parent.ChildNodes.RemoveAt(index);
405
                                parent.ChildNodes.Insert(index, coveringSpan);
406
                            }
407
                        }
408
                    }
409
                }
410
            }
411
        }
412

    
413
        private void GenerateCSS(List<AnnotationTagGeneric> tags)
414
        {
415
            /*string inner = "span.annotation {border-bottom: 2px solid;}";
416
            inner += "span {line-height: 30px}\n";*/
417

    
418
            var tagPaddingDict = Intersections.ColorGraph(Intersections.FindIntersections(tags));
419
            foreach (var tag in tags.DistinctBy(t => t.Instance))
420
            {
421
                var padding = (tagPaddingDict[tag] + 2) * 2;
422
                TagInstanceCSS.Add(new()
423
                {
424
                    InstanceId = tag.Instance,
425
                    Color = tag.Tag.Color,
426
                    Padding = padding
427
                });
428
            }
429
        }
430

    
431
        private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId)
432
        {
433
            foreach (var node in descendantsOriginal)
434
            {
435
                var originalNode = node;
436
                var toEditNode = NodeDict[node];
437

    
438
                if (originalNode.Name.Contains("#"))
439
                {
440
                    continue;
441
                }
442
                else
443
                {
444
                    TagStartPositions.Add(originalNode.OuterStartIndex);
445
                    TagStartLengths.Add(originalNode.InnerStartIndex - originalNode.OuterStartIndex);
446
                    currentId = TagStartPositions.Count - 1;
447
                    toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString());
448

    
449
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
450
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
451
                }
452
            }
453
        }
454

    
455
        private void FillNodeDict(IEnumerable<HtmlNode> descendantsOriginal, IEnumerable<HtmlNode> descendantsToEdit)
456
        {
457
            var zipped = descendantsOriginal.Zip(descendantsToEdit, (orig, toEdit) => new
458
            {
459
                Original = orig,
460
                ToEdit = toEdit
461
            });
462
            foreach (var node in zipped)
463
            {
464
                var originalNode = node.Original;
465
                var toEditNode = node.ToEdit;
466
                NodeDict.Add(originalNode, toEditNode);
467
            }
468
        }
469

    
470

    
471
        /*
472
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
473
         */
474

    
475
        /*
476
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
477
         */
478

    
479
        public (string, CachedInfo) PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToAdd, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
480
        {
481
            UnpackCachedInfo(cachedInfo);
482

    
483
            var docOriginal = new HtmlDocument();
484
            docOriginal.LoadHtml(htmlOriginal);
485
            var docToEdit = new HtmlDocument();
486
            docToEdit.LoadHtml(htmlToEdit);
487

    
488
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
489

    
490
            int i = 0;
491
            List<HtmlNode> addedForSelection = new();
492

    
493
            int descendantsCount = descendantsToEdit.Count();
494
            while (i < descendantsCount)
495
            {
496
                for (; i < descendantsCount; i++)
497
                {
498
                    var node = descendantsToEdit.ElementAt(i);
499
                    if (!node.Name.Contains("#text") || addedForSelection.Contains(node) || addedForSelection.Contains(node.ParentNode) ||
500
                        node.ParentNode.Name == "style" || node.ParentNode.Name.StartsWith("#"))
501
                    {
502
                        continue;
503
                    }
504

    
505
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
506

    
507
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
508
                    var end = TagClosingPositions[nodeId];
509

    
510
                    int selectionStart = tagToAdd.Position;
511
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
512

    
513
                    if (selectionStart < end && selectionEnd > start)
514
                    {
515
                        if (selectionStart <= start && selectionEnd >= end)
516
                        {
517
                            addedForSelection.Add(SolveFullFill(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
518
                        }
519
                        else if (selectionStart <= start)
520
                        {
521
                            addedForSelection.AddRange(SolveRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
522
                        }
523
                        else if (selectionEnd >= end)
524
                        {
525
                            addedForSelection.AddRange(SolveLeftGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
526
                        }
527
                        else
528
                        {
529
                            addedForSelection.AddRange(SolveLeftRightGap(node, selectionStart, selectionEnd, start, end, docToEdit, tagToAdd));
530
                        }
531
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
532
                        descendantsCount = descendantsToEdit.Count();
533
                        break;
534
                    }
535
                }
536
            }
537

    
538
            GenerateCSS(tags);
539
            return (docToEdit.DocumentNode.OuterHtml, new()
540
            {
541
                TagStartPositions = TagStartPositions,
542
                TagStartLengths = TagStartLengths,
543
                TagClosingPositions = TagClosingPositions,
544
                TagClosingLengths = TagClosingLengths,
545
                TagInstanceCSS = TagInstanceCSS,
546
                NodeDict = NodeDict
547
            });
548
        }
549

    
550

    
551
        public (string, CachedInfo) PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToRemove, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
552
        {
553
            UnpackCachedInfo(cachedInfo);
554

    
555
            var docOriginal = new HtmlDocument();
556
            docOriginal.LoadHtml(htmlOriginal);
557
            var docToEdit = new HtmlDocument();
558
            docToEdit.LoadHtml(htmlToEdit);
559

    
560
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
561

    
562
            int i = 0;
563
            int descendantsCount = descendantsToEdit.Count();
564
            while (i < descendantsCount)
565
            {
566
                for (; i < descendantsCount; i++)
567
                {
568
                    var node = descendantsToEdit.ElementAt(i);
569
                    if (!node.Attributes.Contains(TAG_EF_ID_ATTRIBUTE_NAME))
570
                    {
571
                        continue;
572
                    }
573
                    else
574
                    {
575
                        if (node.Attributes[TAG_EF_ID_ATTRIBUTE_NAME].Value != tagToRemove.Id.ToString())
576
                        {
577
                            continue;
578
                        }
579

    
580
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
581
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
582
                        node.Attributes.Remove("class");
583
                        node.Attributes.Remove("start");
584
                        node.Attributes.Remove("end");
585

    
586
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
587
                        descendantsCount = descendantsToEdit.Count();
588
                        break;
589
                    }
590
                }
591
            }
592

    
593
            GenerateCSS(tags);
594
            return (docToEdit.DocumentNode.OuterHtml, new()
595
            {
596
                TagStartPositions = TagStartPositions,
597
                TagStartLengths = TagStartLengths,
598
                TagClosingPositions = TagClosingPositions,
599
                TagClosingLengths = TagClosingLengths,
600
                TagInstanceCSS = TagInstanceCSS,
601
                NodeDict = NodeDict
602
            });
603
        }
604

    
605
        /*
606
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
607
         */
608
    }
609
}
(1-1/2)