-
Notifications
You must be signed in to change notification settings - Fork 1
Parse plain-text catalog dependency cells into typed relation edges #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1191,26 +1191,55 @@ function parseLabeledRelations( | |||||||||||||||||||||||||||||||||||||||||||||||||
| sourceCitation: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ): RelationEdge[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const relationEdges: RelationEdge[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const relationRegex = | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Bold markdown format: **Builds on:** A.1, A.2. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const boldRegex = | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /\*\*([^:*]+):\*\*\s*([\s\S]*?)(?=(?:\n\s*[*-]\s*\*\*[^:*]+:\*\*|\s+\*\*[^:*]+:\*\*|$))/g; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const match of text.matchAll(relationRegex)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const label = normalizeForLookup(match[1] ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const relation = RELATION_LABELS[label]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!relation) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const target of extractIds(match[2] ?? '')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| relationEdges.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from: sourceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| relation, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| to: target, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| source: sourceCitation, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const match of text.matchAll(boldRegex)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pushRelationEdges(relationEdges, sourceId, match[1] ?? '', match[2] ?? '', sourceCitation); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Plain-text catalog format: Builds on: A.1, A.2. Constrains: B.3. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Uses lookahead to split on the next known label or end-of-string. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (relationEdges.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const escapedLabels = Object.keys(RELATION_LABELS).map( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| (label) => label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const labelAlternation = escapedLabels.join('|'); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const plainRegex = new RegExp( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| `(${labelAlternation}):\\s*(.*?)(?=(?:${labelAlternation}):|$)`, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The plain-text regex only terminates a relation value at the next known relation label or end-of-string, so IDs that belong to other labels get misattributed to the previous relation. For example, Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||
| 'gis', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1205
to
+1212
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Comment on lines
+1204
to
+1212
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const match of text.matchAll(plainRegex)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pushRelationEdges(relationEdges, sourceId, match[1] ?? '', match[2] ?? '', sourceCitation); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1204
to
1216
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parse plain-text labels even when bold labels already matched. Line 1204 makes plain-text parsing conditional on zero bold matches, which drops edges for mixed-format cells (e.g., 💡 Suggested fix- if (relationEdges.length === 0) {
- const escapedLabels = Object.keys(RELATION_LABELS).map(
- (label) => label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
- );
- const labelAlternation = escapedLabels.join('|');
- const plainRegex = new RegExp(
- `(${labelAlternation}):\\s*(.*?)(?=(?:${labelAlternation}):|$)`,
- 'gis',
- );
- for (const match of text.matchAll(plainRegex)) {
- pushRelationEdges(relationEdges, sourceId, match[1] ?? '', match[2] ?? '', sourceCitation);
- }
- }
+ const escapedLabels = Object.keys(RELATION_LABELS).map(
+ (label) => label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
+ );
+ const labelAlternation = escapedLabels.join('|');
+ const plainRegex = new RegExp(
+ `(${labelAlternation}):\\s*(.*?)(?=(?:${labelAlternation}):|$)`,
+ 'gis',
+ );
+ for (const match of text.matchAll(plainRegex)) {
+ pushRelationEdges(relationEdges, sourceId, match[1] ?? '', match[2] ?? '', sourceCitation);
+ }📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.42.1)[warning] 1208-1211: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns. (regexp-from-variable) 🤖 Prompt for AI Agents
Comment on lines
+1202
to
1216
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return relationEdges; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| function pushRelationEdges( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| edges: RelationEdge[], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rawLabel: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rawTargets: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceCitation: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const label = normalizeForLookup(rawLabel); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const relation = RELATION_LABELS[label]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!relation) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const target of extractIds(rawTargets)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| edges.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from: sourceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| relation, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| to: target, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| source: sourceCitation, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| function parseKeywords(cell: string): string[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const match = cell.match(/Keywords:\s*(.+?)(?:Queries:|$)/i); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!match) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback condition is
relationEdges.length === 0, which means the plain-text parser will run not only when the bold regex finds zero matches, but also when bold matches exist but produce zero edges (e.g., unknown labels or labels with no parseable IDs). If the intended behavior is “only fallback when the bold-markdown regex finds zero matches” (per PR description), track whetherboldRegexmatched at least once (separately from edge count) and use that to gate the fallback, or update the PR description to reflect the actual behavior.