Skip to content

Commit 38e6462

Browse files
authored
Merge pull request #15 from dplabs/fix/issue-4-property-extraction
fix: only extract properties from leading front-matter block
2 parents 85e3de6 + 2292436 commit 38e6462

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

src/__tests__/parser.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,36 @@ describe('Parser', () => {
197197
});
198198

199199
it('removes extracted properties from content', () => {
200-
expect(parser.parse('name: a\n1')[0].content).toEqual(['\n1']);
200+
expect(parser.parse('name: a\n1')[0].content).toEqual(['1']);
201+
});
202+
203+
it('does not consume a word:value line that appears after other content (issue #4)', () => {
204+
// "Example: this line disappears" appears after real content — must stay as content.
205+
const slide = parser.parse('# Heading\n\nExample: this line disappears\n\nMore content.')[0];
206+
expect(slide.properties['Example']).toBeUndefined();
207+
expect(slide.content.join('')).toContain('Example: this line disappears');
208+
});
209+
210+
it('does not consume a word:value line mixed in with regular content', () => {
211+
const slide = parser.parse('Some intro\n\nNote: this should stay\n\nMore text')[0];
212+
expect(slide.properties['Note']).toBeUndefined();
213+
expect(slide.content.join('')).toContain('Note: this should stay');
214+
});
215+
216+
it('stops extracting properties at the first non-property line', () => {
217+
// "name" is a valid property but the next line is not, so class:b further down stays as content.
218+
const slide = parser.parse('name: a\nNot valid prop\nclass: b')[0];
219+
expect(slide.properties.name).toBe('a');
220+
expect(slide.properties.class).toBeUndefined();
221+
expect(slide.content.join('')).toContain('class: b');
222+
});
223+
224+
it('stops extracting properties at a blank line separating front-matter from content', () => {
225+
const slide = parser.parse('name: a\n\nclass: b')[0];
226+
expect(slide.properties.name).toBe('a');
227+
// class: b is after a blank line — it is content, not a property
228+
expect(slide.properties.class).toBeUndefined();
229+
expect(slide.content.join('')).toContain('class: b');
201230
});
202231
});
203232

src/mdeck/parser.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,39 @@ function appendTo(element: ParsedSlide | ContentClass, content: ContentItem): vo
114114
}
115115

116116
function extractProperties(source: string, properties: Record<string, string>): string {
117-
const propertyFinder = /^\n*([-\w]+):([^$\n]*)|\n*(?:<!--\s*)([-\w]+):([^$\n]*?)(?:\s*-->)/i;
117+
if (typeof source !== 'string') return source as unknown as string;
118+
119+
// Extract inline HTML comment properties anywhere in the source (they are invisible markers).
120+
const commentFinder = /\n*(?:<!--\s*)([-\w]+):([^$\n]*?)(?:\s*-->)/gi;
118121
let match: RegExpExecArray | null;
119-
while ((match = propertyFinder.exec(source)) !== null) {
122+
while ((match = commentFinder.exec(source)) !== null) {
120123
source = source.slice(0, match.index) + source.slice(match.index + match[0].length);
121-
if (match[1] !== undefined) {
122-
properties[match[1].trim()] = match[2].trim();
123-
} else {
124-
properties[match[3].trim()] = match[4].trim();
125-
}
126-
propertyFinder.lastIndex = match.index;
124+
properties[match[1].trim()] = match[2].trim();
125+
commentFinder.lastIndex = match.index;
126+
}
127+
128+
// Extract plain `key: value` properties only from the leading front-matter block.
129+
// Stop as soon as a line that is not a property (or blank) is encountered so that
130+
// content lines like "Example: this disappears" are never consumed as properties.
131+
const lines = source.split('\n');
132+
const consumed: number[] = [];
133+
const propertyLine = /^([-\w]+):([^$\n]*)$/i;
134+
let i = 0;
135+
// Skip leading blank lines.
136+
while (i < lines.length && lines[i].trim() === '') { i++; }
137+
// Consume contiguous property lines.
138+
while (i < lines.length) {
139+
const line = lines[i];
140+
if (line.trim() === '') break; // blank line ends the front-matter block
141+
const m = propertyLine.exec(line);
142+
if (!m) break; // non-property line ends the block
143+
properties[m[1].trim()] = m[2].trim();
144+
consumed.push(i);
145+
i++;
127146
}
128-
return source;
147+
// Remove consumed lines from source.
148+
const remaining = lines.filter((_, idx) => !consumed.includes(idx));
149+
return remaining.join('\n');
129150
}
130151

131152
function cleanInput(source: string): string {

0 commit comments

Comments
 (0)