Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | 2x 2x 16x 16x 2x 8x 3x 5x 5x 18x 7x 7x 7x 7x 7x 5x 5x 5x 11x 11x 13x 5x 5x 23x 5x 18x 18x 19x 20x 19x 19x 21x 21x 21x 21x 21x 19x 18x 4x 14x 14x 14x 14x 5x 5x 5x 5x 14x 14x 14x 19x 19x 14x 2x 15x 15x 13x 15x 15x 15x 15x 6x 9x 8x 8x 8x 8x 10x 10x 10x 10x 10x 8x 7x 15x | import { Strings } from "../Strings";
import { RuleViolation, ViolationGroup } from "./types/AnalysisTypes";
import { HeaderSection } from "./types/interfaces";
/**
* Escape HTML and apply content highlighting to show rule violation patterns
* @param content - The text content to escape and highlight
* @param violationGroups - Array of violation groups with highlight patterns
* @returns The HTML-escaped content with highlighting spans applied
*/
export function escapeAndHighlight(content: string, violationGroups: ViolationGroup[]): string {
const escapedContent = Strings.htmlEncode(content);
return highlightContent(escapedContent, violationGroups);
}
/**
* Apply highlighting to HTML content without breaking existing HTML tags
* Use this for content that already contains HTML (like valueUrl with anchor tags)
* @param htmlContent - HTML content (e.g., content with anchor tags from mapHeaderToURL)
* @param violationGroups - Array of violation groups with highlight patterns
* @returns The HTML content with highlighting spans applied to text nodes only
*/
export function highlightHtml(htmlContent: string, violationGroups: ViolationGroup[]): string {
if (!htmlContent || !violationGroups || violationGroups.length === 0) {
return htmlContent;
}
// Create a temporary DOM element to parse the HTML
const temp = document.createElement("div");
temp.innerHTML = htmlContent;
// Function to highlight text nodes recursively without breaking HTML structure
function highlightTextNodes(node: Node): void {
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent || "";
Eif (text.trim()) {
// Escape the text content before highlighting to prevent XSS
const escapedText = Strings.htmlEncode(text);
const highlighted = highlightContent(escapedText, violationGroups);
if (highlighted !== escapedText) {
const span = document.createElement("span");
span.innerHTML = highlighted;
node.parentNode?.replaceChild(span, node);
}
}
} else Eif (node.nodeType === Node.ELEMENT_NODE) {
const childNodes = Array.from(node.childNodes);
childNodes.forEach(child => highlightTextNodes(child));
}
}
highlightTextNodes(temp);
return temp.innerHTML;
}
/**
* Apply content highlighting to show rule violation patterns
* @param content - The text content to highlight (should already be HTML-escaped)
* @param violationGroups - Array of violation groups with highlight patterns
* @returns The content with HTML highlighting spans applied
*/
function highlightContent(content: string, violationGroups: ViolationGroup[]): string {
if (!content || !violationGroups || violationGroups.length === 0) {
return content;
}
interface Match {
start: number;
end: number;
text: string;
}
// Collect all matches first without modifying content
const allMatches: Match[] = [];
violationGroups.forEach(group => {
group.violations.forEach(violation => {
if (violation.highlightPattern) {
const patterns = violation.highlightPattern.split("|");
patterns.forEach(pattern => {
Eif (pattern && pattern.trim()) {
const escapedPattern = pattern.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
try {
const regex = new RegExp(escapedPattern, "gi");
let match;
while ((match = regex.exec(content)) !== null) {
allMatches.push({
start: match.index,
end: match.index + match[0].length,
text: match[0]
});
}
} catch (error) {
console.warn("Invalid regex pattern:", pattern, error);
}
}
});
}
});
});
if (allMatches.length === 0) {
return content;
}
// Sort by position and merge overlapping matches
allMatches.sort((a, b) => a.start - b.start);
const mergedMatches: Match[] = [];
let current = allMatches[0]!;
for (let i = 1; i < allMatches.length; i++) {
const next = allMatches[i]!;
Iif (next.start < current.end) {
// Overlapping - extend current if needed
if (next.end > current.end) {
current = {
start: current.start,
end: next.end,
text: content.slice(current.start, next.end)
};
}
} else {
// Non-overlapping - save current and move to next
mergedMatches.push(current);
current = next;
}
}
mergedMatches.push(current);
// Build result by inserting spans from end to start (preserves positions)
let result = content;
for (let i = mergedMatches.length - 1; i >= 0; i--) {
const match = mergedMatches[i]!;
result =
result.slice(0, match.start) +
`<span class="highlight-violation">${match.text}</span>` +
result.slice(match.end);
}
return result;
}
/**
* Find violations that apply to a specific row by matching section and content
*/
export function getViolationsForRow(
row: { id?: string; label?: string; valueUrl?: string; value?: string; header?: string; headerName?: string },
violationGroups: ViolationGroup[]
): RuleViolation[] {
const matchingViolations: RuleViolation[] = [];
violationGroups.forEach(group => {
group.violations.forEach(violation => {
// Check if violation applies to this row via any of its affected sections
const matchesSection = violation.affectedSections.some(section => {
const headerSection = section as HeaderSection;
// Match by section header/name or headerName property
return headerSection.header === row.label ||
headerSection.header === row.header ||
headerSection.header === row.headerName;
});
if (matchesSection) {
matchingViolations.push(violation);
} else if (violation.highlightPattern) {
// Check if violation pattern matches row content
const content = row.valueUrl || row.value;
Eif (content) {
const patterns = violation.highlightPattern.split("|");
const hasMatch = patterns.some(pattern => {
Eif (pattern && pattern.trim()) {
try {
const escapedPattern = pattern.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(escapedPattern, "gi");
return regex.test(content);
} catch {
return false;
}
}
return false;
});
if (hasMatch) {
matchingViolations.push(violation);
}
}
}
});
});
return matchingViolations;
}
|