flex-md 4.2.1 → 4.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extract/extract.js +19 -1
- package/dist/md/parse.js +6 -3
- package/dist/ofs/parser.js +13 -0
- package/package.json +2 -2
package/dist/extract/extract.js
CHANGED
|
@@ -3,7 +3,25 @@ import { parseHeadingsAndSections, extractBullets, normalizeName } from "../md/p
|
|
|
3
3
|
* Extracts sections, lists, and tables from Markdown based on the OFS.
|
|
4
4
|
*/
|
|
5
5
|
export function extractFromMarkdown(md, spec) {
|
|
6
|
-
|
|
6
|
+
// 0. Robustness: check for fenced block that might contain the target content
|
|
7
|
+
// Highly relevant for LLM responses where the model occasionally wraps everything in a container
|
|
8
|
+
// even if not strictly asked, or if the user provided unframed content but we have L2+ expectations elsewhere.
|
|
9
|
+
const rxFence = /```(?:markdown|flexmd)?\s*\n([\s\S]*?)\n```/gi;
|
|
10
|
+
const matches = Array.from(md.matchAll(rxFence));
|
|
11
|
+
let workingContent = md;
|
|
12
|
+
if (matches.length === 1) {
|
|
13
|
+
const content = matches[0][1];
|
|
14
|
+
// If the content inside the fence has more required sections than outside, use it
|
|
15
|
+
const parsedOutside = parseHeadingsAndSections(md);
|
|
16
|
+
const parsedInside = parseHeadingsAndSections(content);
|
|
17
|
+
const specNorms = new Set(spec.sections.map(s => normalizeName(s.name)));
|
|
18
|
+
const countOutside = parsedOutside.filter(p => specNorms.has(normalizeName(p.heading.name))).length;
|
|
19
|
+
const countInside = parsedInside.filter(p => specNorms.has(normalizeName(p.heading.name))).length;
|
|
20
|
+
if (countInside >= countOutside && countInside > 0) {
|
|
21
|
+
workingContent = content;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const parsed = parseHeadingsAndSections(workingContent);
|
|
7
25
|
const sectionsByName = {};
|
|
8
26
|
const tables = [];
|
|
9
27
|
const specMap = new Map(spec.sections.map(s => [normalizeName(s.name), s]));
|
package/dist/md/parse.js
CHANGED
|
@@ -29,8 +29,10 @@ export function extractFencedBlocks(text) {
|
|
|
29
29
|
return blocks;
|
|
30
30
|
}
|
|
31
31
|
export function parseHeadingsAndSections(md) {
|
|
32
|
-
// Standard headings #... and alternative ===key
|
|
33
|
-
|
|
32
|
+
// Standard headings #... and alternative ===key.
|
|
33
|
+
// Use [ \t]* instead of \s for the trailing space to avoid matching newlines incorrectly with certain configurations.
|
|
34
|
+
// Also include \r? to handle CRLF if needed, although m and g should handle ^\$ correctly.
|
|
35
|
+
const rx = /^((?:#{1,6})[ \t]+(.+?)[ \t]*|===(.+?)[ \t]*)$/gm;
|
|
34
36
|
const headings = [];
|
|
35
37
|
let m;
|
|
36
38
|
while ((m = rx.exec(md)) !== null) {
|
|
@@ -42,7 +44,8 @@ export function parseHeadingsAndSections(md) {
|
|
|
42
44
|
name = (m[3] ?? "").trim();
|
|
43
45
|
}
|
|
44
46
|
else {
|
|
45
|
-
const
|
|
47
|
+
const hashesMatch = full.match(/^#+/);
|
|
48
|
+
const hashes = hashesMatch ? hashesMatch[0] : "";
|
|
46
49
|
level = hashes.length;
|
|
47
50
|
name = (m[2] ?? "").trim();
|
|
48
51
|
}
|
package/dist/ofs/parser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeName } from "../md/parse.js";
|
|
1
2
|
/**
|
|
2
3
|
* Validate a format specification.
|
|
3
4
|
* Returns detailed validation results.
|
|
@@ -138,6 +139,18 @@ export function parseOutputFormatSpec(md, opts = {}) {
|
|
|
138
139
|
}
|
|
139
140
|
continue;
|
|
140
141
|
}
|
|
142
|
+
// heading items (e.g. ### Short Answer)
|
|
143
|
+
const headingMatch = line.match(/^#{1,6}\s+(.+)$/);
|
|
144
|
+
if (headingMatch) {
|
|
145
|
+
const name = headingMatch[1].trim();
|
|
146
|
+
// Don't re-parse "Output format" itself if it somehow gets in here
|
|
147
|
+
if (normalizeName(name) !== "output format") {
|
|
148
|
+
const s = { name, kind: "text" };
|
|
149
|
+
sections.push(s);
|
|
150
|
+
currentSection = s;
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
141
154
|
// If not a bullet and we have a current section, it's an instruction
|
|
142
155
|
if (currentSection && line.length > 0) {
|
|
143
156
|
// Support "Columns: A, B, C" in instructions for tables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flex-md",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.5",
|
|
4
4
|
"description": "Parse and stringify FlexMD: semi-structured Markdown with three powerful layers - Frames, Output Format Spec (OFS), and Detection/Extraction.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "",
|
|
@@ -50,6 +50,6 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"nd": "^1.2.0",
|
|
52
52
|
"nx-helpers": "^1.5.0",
|
|
53
|
-
"nx-md-parser": "^
|
|
53
|
+
"nx-md-parser": "^2.0.1"
|
|
54
54
|
}
|
|
55
55
|
}
|