Projekt

Obecné

Profil

Stáhnout (24.5 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("href");
129
            sanitizer.AllowedAttributes.Add("end");
130
            sanitizer.AllowedAttributes.Add("start");
131
            sanitizer.AllowedAttributes.Add("class");
132
            sanitizer.AllowedAttributes.Add("target");
133
            if (sanitizer.AllowedTags.Contains("script"))
134
            {
135
                sanitizer.AllowedTags.Remove("script");
136
            }
137
            if (!sanitizer.AllowedTags.Contains("style"))
138
            {
139
                sanitizer.AllowedTags.Add("style");
140
            }
141
            sanitizer.AllowedTags.Add("a");
142
            docToRender = sanitizer.Sanitize(docToRender);
143
            GenerateCSS(tags);
144

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

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

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

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

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

    
174
            return spanSelected;
175
        }
176

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
280
            return span;
281
        }
282

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

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

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

    
323
                    var parent = NodeDict[documentNode];
324

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

    
328
                    return;
329
                }
330
            }
331

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

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

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

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

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

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

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

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

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

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

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

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

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

    
446

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

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

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

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

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

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

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

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

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

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

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

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

    
526

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

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

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

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

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

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

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

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