Projekt

Obecné

Profil

Stáhnout (24.6 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
            if (sanitizer.AllowedTags.Contains("script"))
135
            {
136
                sanitizer.AllowedTags.Remove("script");
137
            }
138
            if (!sanitizer.AllowedTags.Contains("style"))
139
            {
140
                sanitizer.AllowedTags.Add("style");
141
            }
142
            sanitizer.AllowedTags.Add("a");
143
            docToRender = sanitizer.Sanitize(docToRender);
144
            GenerateCSS(tags);
145

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

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

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

    
166
            EPosition markerPosition = EPosition.MARK_NONE;
167
            if (selectionEnd == end && selectionStart == start)
168
            {
169
                markerPosition = EPosition.MARK_LEFT_RIGHT;
170
            }
171

    
172
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, start, markerPosition);
173
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
174

    
175
            return spanSelected;
176
        }
177

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

    
186
            var parentNode = node.ParentNode;
187
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
188
            parentNode.ChildNodes.RemoveAt(nodeIndex);
189

    
190
            int spanSelectedStart = start;
191
            int spanAfterStart = start + textSelected.Length;
192

    
193
            HtmlNode spanSelected = CreateSpan(docToEdit, textSelected, TagStartPositions.Count, tag.Instance, tag.Id, spanSelectedStart, EPosition.MARK_RIGHT);
194
            parentNode.ChildNodes.Insert(nodeIndex, spanSelected);
195

    
196
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
197
            parentNode.ChildNodes.Insert(nodeIndex + 1, spanAfter);
198

    
199
            return new() { spanSelected, spanAfter };
200
        }
201

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

    
210
            var parentNode = node.ParentNode;
211
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
212
            parentNode.ChildNodes.RemoveAt(nodeIndex);
213

    
214
            int spanBeforeStart = start;
215
            int spanSelectedStart = start + textBefore.Length;
216

    
217
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
218
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
219

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

    
223
            return new() { spanSelected, spanBefore };
224
        }
225

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

    
235
            var parentNode = node.ParentNode;
236
            int nodeIndex = parentNode.ChildNodes.IndexOf(node);
237
            parentNode.ChildNodes.RemoveAt(nodeIndex);
238

    
239
            int spanBeforeStart = start;
240
            int spanSelectedStart = start + textBefore.Length;
241
            int spanAfterStart = start + textBefore.Length + textSelected.Length;
242

    
243
            HtmlNode spanBefore = CreateSpan(docToEdit, textBefore, TagStartPositions.Count, null, null, spanBeforeStart);
244
            parentNode.ChildNodes.Insert(nodeIndex, spanBefore);
245

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

    
249
            HtmlNode spanAfter = CreateSpan(docToEdit, textAfter, TagStartPositions.Count, null, null, spanAfterStart);
250
            parentNode.ChildNodes.Insert(nodeIndex + 2, spanAfter);
251

    
252
            return new() { spanSelected, spanBefore, spanAfter };
253
        }
254

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

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

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

    
281
            return span;
282
        }
283

    
284
        private enum EPosition
285
        {
286
            MARK_LEFT = 5,
287
            MARK_RIGHT = 3,
288
            MARK_LEFT_RIGHT = 2,
289
            MARK_NONE = 0
290
        }
291

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

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

    
324
                    var parent = NodeDict[documentNode];
325

    
326
                    parent.ChildNodes.RemoveAt(0);
327
                    parent.ChildNodes.Add(coveringSpan);
328

    
329
                    return;
330
                }
331
            }
332

    
333
            foreach (var node in descendantsOriginal)
334
            {
335
                var originalNode = node;
336
                var toEditNode = NodeDict[node];
337

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

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

    
359
                    if (onlyText || onlySubtags)
360
                    {
361
                        continue;
362
                    }
363
                    else
364
                    {
365

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

    
378
                                var parent = NodeDict[node];
379
                                var index = parent.ChildNodes.IndexOf(NodeDict[child]);
380

    
381
                                parent.ChildNodes.RemoveAt(index);
382
                                parent.ChildNodes.Insert(index, coveringSpan);
383
                            }
384
                        }
385
                    }
386
                }
387
            }
388
        }
389

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

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

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

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

    
426
                    TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength);
427
                    TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength));
428
                }
429
            }
430
        }
431

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

    
447

    
448
        /*
449
         *      Full HTML Preprocessing -------------------------------------------------------------------------------
450
         */
451

    
452
        /*
453
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
454
         */
455

    
456
        public (string, CachedInfo) PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToAdd, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
457
        {
458
            UnpackCachedInfo(cachedInfo);
459

    
460
            var docOriginal = new HtmlDocument();
461
            docOriginal.LoadHtml(htmlOriginal);
462
            var docToEdit = new HtmlDocument();
463
            docToEdit.LoadHtml(htmlToEdit);
464

    
465
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
466

    
467
            int i = 0;
468
            List<HtmlNode> addedForSelection = new();
469

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

    
482
                    int nodeId = node.ParentNode.GetAttributeValue(TAG_ID_ATTRIBUTE_NAME, -1);
483

    
484
                    var start = TagStartPositions[nodeId] + TagStartLengths[nodeId];
485
                    var end = TagClosingPositions[nodeId];
486

    
487
                    int selectionStart = tagToAdd.Position;
488
                    int selectionEnd = tagToAdd.Position + tagToAdd.Length;
489

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

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

    
527

    
528
        public (string, CachedInfo) PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToRemove, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo)
529
        {
530
            UnpackCachedInfo(cachedInfo);
531

    
532
            var docOriginal = new HtmlDocument();
533
            docOriginal.LoadHtml(htmlOriginal);
534
            var docToEdit = new HtmlDocument();
535
            docToEdit.LoadHtml(htmlToEdit);
536

    
537
            var descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
538

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

    
557
                        node.Attributes.Remove(TAG_EF_ID_ATTRIBUTE_NAME);
558
                        node.Attributes.Remove(TAG_INSTANCE_ATTRIBUTE_NAME);
559
                        node.Attributes.Remove("class");
560
                        node.Attributes.Remove("start");
561
                        node.Attributes.Remove("end");
562

    
563
                        descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList();
564
                        descendantsCount = descendantsToEdit.Count();
565
                        break;
566
                    }
567
                }
568
            }
569

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

    
582
        /*
583
         *      Partial HTML Preprocessing ----------------------------------------------------------------------------
584
         */
585
    }
586
}
(1-1/2)