Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/removals/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ import { DebugRemoval } from '../types';
import { textPreview, logDebug } from '../utils';
import { getClassName, hasResponsiveShowClass } from '../utils/dom';

function hasSubstantiveTagsContent(el: Element): boolean {
if ((el.id || '').toLowerCase() !== 'tags') return false;
if (!el.querySelector('h1, h2, h3, h4, h5, h6')) return false;

const text = el.textContent?.trim() || '';
if (!text) return false;

const linkText = Array.from(el.querySelectorAll('a'))
.map(a => a.textContent || '')
.join(' ')
.trim();
if (linkText && linkText.length / text.length > 0.7) return false;

return Array.from(el.querySelectorAll('p')).some(p => {
const paragraph = p.textContent?.trim() || '';
const words = paragraph.split(/\s+/).filter(Boolean).length;
return words >= 2 && /[.!?。!?]/.test(paragraph);
});
}

export function removeBySelector(doc: Document, debug: boolean, removeExact: boolean = true, removePartial: boolean = true, mainContent?: Element | null, debugRemovals?: DebugRemoval[], skipHiddenExactSelectors: boolean = false) {
const startTime = Date.now();
let exactSelectorCount = 0;
Expand Down Expand Up @@ -43,6 +63,9 @@ export function removeBySelector(doc: Document, debug: boolean, removeExact: boo
if (el.matches(HIDDEN_EXACT_SELECTOR) && hasResponsiveShowClass(getClassName(el))) {
return;
}
if (hasSubstantiveTagsContent(el)) {
return;
}
elementsToRemove.set(el, { type: 'exact' });
exactSelectorCount++;
}
Expand Down
42 changes: 42 additions & 0 deletions tests/issues-321-tags-section.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, test, expect } from 'vitest';
import { Defuddle } from '../src/node';
import { parseDocument } from './helpers';

const html = `
<!DOCTYPE html>
<html>
<head><title>Tags section test</title></head>
<body>
<article>
<h1>Article title</h1>
<h2>Intro heading</h2>
<p>Intro paragraph with enough text to be treated as content.</p>

<section id="tags">
<h2>Content heading</h2>
<p>Content paragraph that should remain in the extracted output.</p>
</section>

<footer id="tags">
<a href="/tags/alpha">alpha</a>
<a href="/tags/beta">beta</a>
</footer>
</article>
</body>
</html>
`;

describe('issue #321', () => {
test('keeps substantive content sections with id="tags"', async () => {
const result = await Defuddle(parseDocument(html, 'https://example.com/tags-section'), 'https://example.com/tags-section', {
separateMarkdown: true,
});

expect(result.contentMarkdown).toContain('## Intro heading');
expect(result.contentMarkdown).toContain('Intro paragraph with enough text');
expect(result.contentMarkdown).toContain('## Content heading');
expect(result.contentMarkdown).toContain('Content paragraph that should remain');
expect(result.contentMarkdown).not.toContain('/tags/alpha');
expect(result.contentMarkdown).not.toContain('/tags/beta');
});
});