Revize a35cb648
Přidáno uživatelem Vojtěch Bartička před asi 2 roky(ů)
Backend/Core/Services/HTMLService/HTMLService.cs | ||
---|---|---|
13 | 13 |
{ |
14 | 14 |
public class HTMLService : IHTMLService |
15 | 15 |
{ |
16 |
|
|
16 |
/// <summary> |
|
17 |
/// Attribute name for uniquely identifying each tag in the HTML document on the frontend |
|
18 |
/// </summary> |
|
17 | 19 |
private const string TAG_ID_ATTRIBUTE_NAME = "aswi-tag-id"; |
20 |
|
|
21 |
/// <summary> |
|
22 |
/// Attribute name for tag instance so that each marked out tag can be given a style |
|
23 |
/// </summary> |
|
18 | 24 |
private const string TAG_INSTANCE_ATTRIBUTE_NAME = "aswi-tag-instance"; |
25 |
|
|
26 |
/// <summary> |
|
27 |
/// Attribute name for EF Tag IDs so that the frontend can reference them to the backend for deletion |
|
28 |
/// </summary> |
|
19 | 29 |
private const string TAG_EF_ID_ATTRIBUTE_NAME = "aswi-tag-ef-id"; |
20 | 30 |
|
31 |
/// <summary> |
|
32 |
/// Info that is cached in tha database |
|
33 |
/// </summary> |
|
21 | 34 |
public class CachedInfo |
22 | 35 |
{ |
36 |
/// <summary> |
|
37 |
/// Positions of opening tags in the original HTML document |
|
38 |
/// </summary> |
|
23 | 39 |
public List<int> TagStartPositions = new(); |
40 |
/// <summary> |
|
41 |
/// Lengths of opening tags in the original HTML document |
|
42 |
/// </summary> |
|
24 | 43 |
public List<int> TagStartLengths = new(); |
44 |
/// <summary> |
|
45 |
/// Positions of closing tags in the original HTML document |
|
46 |
/// </summary> |
|
25 | 47 |
public List<int> TagClosingPositions = new(); |
48 |
/// <summary> |
|
49 |
/// Lengths for closing tags in the original HTML document |
|
50 |
/// </summary> |
|
26 | 51 |
public List<int> TagClosingLengths = new(); |
52 |
/// <summary> |
|
53 |
/// Unused |
|
54 |
/// </summary> |
|
27 | 55 |
public Dictionary<HtmlNode, HtmlNode> NodeDict = new(); |
56 |
/// <summary> |
|
57 |
/// Cached CSS for each tag |
|
58 |
/// </summary> |
|
28 | 59 |
public List<TagInstanceCSSInfo> TagInstanceCSS = new(); |
29 | 60 |
} |
30 | 61 |
|
... | ... | |
35 | 66 |
private Dictionary<HtmlNode, HtmlNode> NodeDict = new(); |
36 | 67 |
private List<TagInstanceCSSInfo> TagInstanceCSS = new(); |
37 | 68 |
|
69 |
/// <summary> |
|
70 |
/// Unpack cached info into local (request-scoped) variables |
|
71 |
/// </summary> |
|
72 |
/// <param name="cachedInfo"></param> |
|
38 | 73 |
private void UnpackCachedInfo(CachedInfo cachedInfo) |
39 | 74 |
{ |
40 | 75 |
TagStartPositions = cachedInfo.TagStartPositions; |
... | ... | |
62 | 97 |
|
63 | 98 |
int currentId = 0; |
64 | 99 |
|
100 |
// Create mapping original-document-node -> to-edit-document-node |
|
65 | 101 |
FillNodeDict(descendantsOriginal, descendantsToEdit); |
102 |
|
|
103 |
// Assign IDs to all original rtags |
|
66 | 104 |
AssignIdsToOriginalDocument(descendantsOriginal, ref currentId); |
67 | 105 |
|
106 |
// Wrap all text that is not in a tag or where the parent node contains tags as children also in spans |
|
68 | 107 |
WrapTextInSpan(descendantsOriginal, docToEdit); |
69 | 108 |
|
70 | 109 |
descendantsToEdit = docToEdit.DocumentNode.DescendantsAndSelf().ToList(); |
... | ... | |
156 | 195 |
}); |
157 | 196 |
} |
158 | 197 |
|
198 |
/// <summary> |
|
199 |
/// Wrap text in span when the target text is the whole text of the target tag |
|
200 |
/// </summary> |
|
201 |
/// <param name="node">text node to wrap</param> |
|
202 |
/// <param name="selectionStart">start of the selection in the edited document</param> |
|
203 |
/// <param name="selectionEnd">end of the selection in the edited document</param> |
|
204 |
/// <param name="start">start of the node</param> |
|
205 |
/// <param name="end">end of the node</param> |
|
206 |
/// <param name="docToEdit">document to edit</param> |
|
207 |
/// <param name="tag">tag we are adding</param> |
|
208 |
/// <returns></returns> |
|
159 | 209 |
private HtmlNode SolveFullFill(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, AnnotationTagGeneric tag) |
160 | 210 |
{ |
161 | 211 |
// full fill |
... | ... | |
185 | 235 |
return spanSelected; |
186 | 236 |
} |
187 | 237 |
|
238 |
/// <summary> |
|
239 |
/// Wrap text in span when the target text starts at the start of the tag and leaves space on the right side |
|
240 |
/// </summary> |
|
241 |
/// <param name="node">text node to wrap</param> |
|
242 |
/// <param name="selectionStart">start of the selection in the edited document</param> |
|
243 |
/// <param name="selectionEnd">end of the selection in the edited document</param> |
|
244 |
/// <param name="start">start of the node</param> |
|
245 |
/// <param name="end">end of the node</param> |
|
246 |
/// <param name="docToEdit">document to edit</param> |
|
247 |
/// <param name="tag">tag we are adding</param> |
|
248 |
/// <returns></returns> |
|
188 | 249 |
private List<HtmlNode> SolveRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, |
189 | 250 |
AnnotationTagGeneric tag) |
190 | 251 |
{ |
... | ... | |
215 | 276 |
return new() { spanSelected, spanAfter }; |
216 | 277 |
} |
217 | 278 |
|
279 |
/// <summary> |
|
280 |
/// Wrap text in span when the target text ends at the end of the tag but leaves space on the left |
|
281 |
/// </summary> |
|
282 |
/// <param name="node">text node to wrap</param> |
|
283 |
/// <param name="selectionStart">start of the selection in the edited document</param> |
|
284 |
/// <param name="selectionEnd">end of the selection in the edited document</param> |
|
285 |
/// <param name="start">start of the node</param> |
|
286 |
/// <param name="end">end of the node</param> |
|
287 |
/// <param name="docToEdit">document to edit</param> |
|
288 |
/// <param name="tag">tag we are adding</param> |
|
289 |
/// <returns></returns> |
|
218 | 290 |
private List<HtmlNode> SolveLeftGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, |
219 | 291 |
AnnotationTagGeneric tag) |
220 | 292 |
{ |
... | ... | |
245 | 317 |
return new() { spanSelected, spanBefore }; |
246 | 318 |
} |
247 | 319 |
|
320 |
/// <summary> |
|
321 |
/// Wrap text in span when the target text is contained in the node but neither starts or ends match |
|
322 |
/// </summary> |
|
323 |
/// <param name="node">text node to wrap</param> |
|
324 |
/// <param name="selectionStart">start of the selection in the edited document</param> |
|
325 |
/// <param name="selectionEnd">end of the selection in the edited document</param> |
|
326 |
/// <param name="start">start of the node</param> |
|
327 |
/// <param name="end">end of the node</param> |
|
328 |
/// <param name="docToEdit">document to edit</param> |
|
329 |
/// <param name="tag">tag we are adding</param> |
|
330 |
/// <returns></returns> |
|
248 | 331 |
private List<HtmlNode> SolveLeftRightGap(HtmlNode node, int selectionStart, int selectionEnd, int start, int end, HtmlDocument docToEdit, |
249 | 332 |
AnnotationTagGeneric tag) |
250 | 333 |
{ |
... | ... | |
274 | 357 |
return new() { spanSelected, spanBefore, spanAfter }; |
275 | 358 |
} |
276 | 359 |
|
360 |
/// <summary> |
|
361 |
/// Create span utility method |
|
362 |
/// </summary> |
|
363 |
/// <param name="doc">document</param> |
|
364 |
/// <param name="text">inner text of the span</param> |
|
365 |
/// <param name="tagId">ID of the tag (TAG_ID_ATTRIBUTE_NAME)</param> |
|
366 |
/// <param name="instanceId">ID of the instance (TAG_INSTANCE_ATTRIBUTE_NAME)</param> |
|
367 |
/// <param name="entityId">Id of the EF entity (TAG_EF_ID_NAME)</param> |
|
368 |
/// <param name="startPosition"></param> |
|
369 |
/// <param name="position"></param> |
|
370 |
/// <returns></returns> |
|
277 | 371 |
private HtmlNode CreateSpan(HtmlDocument doc, string text, int tagId, Guid? instanceId, Guid? entityId, int startPosition, EPosition position = EPosition.MARK_NONE) |
278 | 372 |
{ |
279 | 373 |
HtmlNode span = doc.CreateElement("span"); |
... | ... | |
312 | 406 |
MARK_NONE = 0 |
313 | 407 |
} |
314 | 408 |
|
409 |
/// <summary> |
|
410 |
/// Redirect all links |
|
411 |
/// </summary> |
|
412 |
/// <param name="descendantsOriginal"></param> |
|
315 | 413 |
private void ModifyLinks(IEnumerable<HtmlNode> descendantsOriginal) |
316 | 414 |
{ |
317 | 415 |
foreach (var descendant in descendantsOriginal) |
... | ... | |
327 | 425 |
} |
328 | 426 |
} |
329 | 427 |
|
428 |
/// <summary> |
|
429 |
/// Wrap all #text nodes in spans |
|
430 |
/// </summary> |
|
431 |
/// <param name="descendantsOriginal"></param> |
|
432 |
/// <param name="docToEdit"></param> |
|
330 | 433 |
private void WrapTextInSpan(IEnumerable<HtmlNode> descendantsOriginal, HtmlDocument docToEdit) |
331 | 434 |
{ |
332 | 435 |
// Special case for non-html documents |
... | ... | |
389 | 492 |
foreach (var child in node.ChildNodes) |
390 | 493 |
{ |
391 | 494 |
if (child.Name.Contains("#text")) |
392 |
{ |
|
495 |
{
|
|
393 | 496 |
HtmlNode coveringSpan = docToEdit.CreateElement("span"); |
394 | 497 |
coveringSpan.InnerHtml = child.InnerHtml; |
498 |
|
|
499 |
// Add length of 0 - in they were not in the original document |
|
395 | 500 |
TagStartPositions.Add(child.InnerStartIndex); |
396 | 501 |
TagStartLengths.Add(0); |
397 | 502 |
TagClosingPositions.Add(child.InnerStartIndex + child.InnerLength); |
... | ... | |
410 | 515 |
} |
411 | 516 |
} |
412 | 517 |
|
518 |
/// <summary> |
|
519 |
/// Generate CSS for all tag instances |
|
520 |
/// </summary> |
|
521 |
/// <param name="tags"></param> |
|
413 | 522 |
private void GenerateCSS(List<AnnotationTagGeneric> tags) |
414 | 523 |
{ |
415 | 524 |
/*string inner = "span.annotation {border-bottom: 2px solid;}"; |
... | ... | |
428 | 537 |
} |
429 | 538 |
} |
430 | 539 |
|
540 |
/// <summary> |
|
541 |
/// Add IDs to all tags in the original document |
|
542 |
/// </summary> |
|
543 |
/// <param name="descendantsOriginal"></param> |
|
544 |
/// <param name="currentId"></param> |
|
431 | 545 |
private void AssignIdsToOriginalDocument(IEnumerable<HtmlNode> descendantsOriginal, ref int currentId) |
432 | 546 |
{ |
433 | 547 |
foreach (var node in descendantsOriginal) |
... | ... | |
446 | 560 |
currentId = TagStartPositions.Count - 1; |
447 | 561 |
toEditNode.Attributes.Add(TAG_ID_ATTRIBUTE_NAME, currentId.ToString()); |
448 | 562 |
|
563 |
// Cross-referencing of the original document to the edited document |
|
449 | 564 |
TagClosingPositions.Add(originalNode.InnerStartIndex + originalNode.InnerLength); |
450 | 565 |
TagClosingLengths.Add((originalNode.OuterStartIndex + originalNode.OuterLength) - (originalNode.InnerStartIndex + originalNode.InnerLength)); |
451 | 566 |
} |
... | ... | |
476 | 591 |
* Partial HTML Preprocessing ---------------------------------------------------------------------------- |
477 | 592 |
*/ |
478 | 593 |
|
594 |
/// <summary> |
|
595 |
/// Incrementally modifies the html to render and cached info by adding a new tag |
|
596 |
/// </summary> |
|
597 |
/// <param name="htmlToEdit"></param> |
|
598 |
/// <param name="htmlOriginal"></param> |
|
599 |
/// <param name="tagToAdd"></param> |
|
600 |
/// <param name="tags"></param> |
|
601 |
/// <param name="cachedInfo"></param> |
|
602 |
/// <returns></returns> |
|
479 | 603 |
public (string, CachedInfo) PartialPreprocessHTMLAddTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToAdd, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo) |
480 | 604 |
{ |
481 | 605 |
UnpackCachedInfo(cachedInfo); |
... | ... | |
547 | 671 |
}); |
548 | 672 |
} |
549 | 673 |
|
550 |
|
|
674 |
/// <summary> |
|
675 |
/// Removing tag effectively just cleans up the attributes and CSS thus has no effect |
|
676 |
/// </summary> |
|
677 |
/// <param name="htmlToEdit"></param> |
|
678 |
/// <param name="htmlOriginal"></param> |
|
679 |
/// <param name="tagToRemove"></param> |
|
680 |
/// <param name="tags"></param> |
|
681 |
/// <param name="cachedInfo"></param> |
|
682 |
/// <returns></returns> |
|
551 | 683 |
public (string, CachedInfo) PartialPreprocessHTMLRemoveTag(string htmlToEdit, string htmlOriginal, AnnotationTagGeneric tagToRemove, List<AnnotationTagGeneric> tags, CachedInfo cachedInfo) |
552 | 684 |
{ |
553 | 685 |
UnpackCachedInfo(cachedInfo); |
Také k dispozici: Unified diff
Added comments to backend