Skip to content

Commit af729a8

Browse files
refactor: Colocate stop node workaround with TODO for revert
1 parent 61b4454 commit af729a8

6 files changed

Lines changed: 42 additions & 65 deletions

File tree

src/common/config.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,6 @@ export const namespacePrefixes = Object.entries(namespaceUris).reduce(
179179
{} as Record<string, string>,
180180
)
181181

182-
// Namespace elements that act as containers for other namespace elements.
183-
// Used during stop node expansion to generate paths through these containers.
184-
export const namespaceContainers = [
185-
'media:group',
186-
'media:content',
187-
'media:group.media:content',
188-
'media:embed',
189-
'podcast:liveitem',
190-
'itunes:owner',
191-
]
192-
193182
export const namespaceStopNodes = [
194183
...acastStopNodes,
195184
...adminStopNodes,

src/common/utils.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { decodeHTML } from 'entities'
22
import type { XMLBuilder } from 'fast-xml-parser'
3-
import { Expression } from 'path-expression-matcher'
43
import type {
54
AnyOf,
65
DateLike,
@@ -912,22 +911,32 @@ export const parseJsonObject = (value: unknown): unknown => {
912911
} catch {}
913912
}
914913

914+
// TODO: Remove expandStopNodes and namespaceContainers after fast-xml-parser
915+
// resolves stop node performance regression. Revert feed configs from
916+
// ...expandStopNodes(namespaceStopNodes, [...]) back to ...namespaceStopNodes.
917+
// https://github.com/NaturalIntelligence/fast-xml-parser/issues/816
918+
919+
// Namespace elements that act as containers for other namespace elements.
920+
const namespaceContainers = [
921+
'media:group',
922+
'media:content',
923+
'media:group.media:content',
924+
'media:embed',
925+
'podcast:liveitem',
926+
'itunes:owner',
927+
]
928+
915929
// Expands wildcard stop nodes (*.prefix:element) into explicit paths for
916930
// each feed container, including paths through namespace containers (e.g.
917931
// media:group, podcast:liveitem). Only crosses containers with leaves from
918-
// the same namespace prefix. Pre-constructs Expression objects once at
919-
// module load to avoid re-parsing on every XMLParser.parse() call.
920-
// When fxp resolves its performance regression, this can be replaced with
921-
// raw wildcard strings passed directly to XMLParser.
922-
export const createStopNodeExpressions = (
923-
wildcardStopNodes: Array<string>,
924-
explicitStopNodes: Array<string>,
932+
// the same namespace prefix.
933+
export const expandStopNodes = (
934+
stopNodes: Array<string>,
925935
feedContainerPaths: Array<string>,
926-
namespaceContainers: Array<string>,
927-
): Array<Expression> => {
936+
): Array<string> => {
928937
const set = new Set<string>()
929938

930-
for (const node of wildcardStopNodes) {
939+
for (const node of stopNodes) {
931940
const suffix = node.slice(2)
932941
const prefix = suffix.split(':')[0]
933942

@@ -942,9 +951,5 @@ export const createStopNodeExpressions = (
942951
}
943952
}
944953

945-
for (const node of explicitStopNodes) {
946-
set.add(node)
947-
}
948-
949-
return [...set].map((node) => new Expression(node))
954+
return [...set]
950955
}

src/feeds/atom/parse/config.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { XMLParser } from 'fast-xml-parser'
2+
import { Expression } from 'path-expression-matcher'
23
import {
3-
namespaceContainers,
44
namespacePrefixes,
55
namespaceStopNodes,
66
namespaceUris,
77
parserConfig,
88
} from '../../../common/config.js'
9-
import { createNamespaceNormalizator, createStopNodeExpressions } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
1010

11-
const feedStopNodes = [
11+
export const stopNodes = [
12+
...expandStopNodes(namespaceStopNodes, ['feed', 'feed.entry', 'feed.entry.source']),
1213
'feed.author.name',
1314
'feed.author.uri',
1415
'feed.author.url', // Atom 0.3.
@@ -70,16 +71,9 @@ const feedStopNodes = [
7071
'feed.entry.modified', // Atom 0.3.
7172
]
7273

73-
const stopNodeExpressions = createStopNodeExpressions(
74-
namespaceStopNodes,
75-
feedStopNodes,
76-
['feed', 'feed.entry', 'feed.entry.source'],
77-
namespaceContainers,
78-
)
79-
8074
export const parser = new XMLParser({
8175
...parserConfig,
82-
stopNodes: stopNodeExpressions,
76+
stopNodes: stopNodes.map((node) => new Expression(node)),
8377
})
8478

8579
export const normalizeNamespaces = createNamespaceNormalizator(namespaceUris, namespacePrefixes, [

src/feeds/rdf/parse/config.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { XMLParser } from 'fast-xml-parser'
2+
import { Expression } from 'path-expression-matcher'
23
import {
3-
namespaceContainers,
44
namespacePrefixes,
55
namespaceStopNodes,
66
namespaceUris,
77
parserConfig,
88
} from '../../../common/config.js'
9-
import { createNamespaceNormalizator, createStopNodeExpressions } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
1010

11-
const feedStopNodes = [
11+
export const stopNodes = [
12+
...expandStopNodes(namespaceStopNodes, [
13+
'rdf:rdf.channel',
14+
'rdf:rdf.item',
15+
'rdf:rdf.image',
16+
'rdf:rdf.textinput',
17+
]),
1218
'rdf:rdf.channel.title',
1319
'rdf:rdf.channel.link',
1420
'rdf:rdf.channel.description',
@@ -24,16 +30,9 @@ const feedStopNodes = [
2430
'rdf:rdf.textinput.link',
2531
]
2632

27-
const stopNodeExpressions = createStopNodeExpressions(
28-
namespaceStopNodes,
29-
feedStopNodes,
30-
['rdf:rdf.channel', 'rdf:rdf.item', 'rdf:rdf.image', 'rdf:rdf.textinput'],
31-
namespaceContainers,
32-
)
33-
3433
export const parser = new XMLParser({
3534
...parserConfig,
36-
stopNodes: stopNodeExpressions,
35+
stopNodes: stopNodes.map((node) => new Expression(node)),
3736
})
3837

3938
export const normalizeNamespaces = createNamespaceNormalizator(namespaceUris, namespacePrefixes, [

src/feeds/rss/parse/config.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { XMLParser } from 'fast-xml-parser'
2+
import { Expression } from 'path-expression-matcher'
23
import {
3-
namespaceContainers,
44
namespacePrefixes,
55
namespaceStopNodes,
66
namespaceUris,
77
parserConfig,
88
} from '../../../common/config.js'
9-
import { createNamespaceNormalizator, createStopNodeExpressions } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
1010

1111
// These elements can appear both inside <channel> and as direct children of
1212
// <rss> in malformed feeds, so stop nodes are generated for both paths.
@@ -35,7 +35,8 @@ const sharedStopNodes = [
3535
'item.source',
3636
]
3737

38-
const feedStopNodes = [
38+
export const stopNodes = [
39+
...expandStopNodes(namespaceStopNodes, ['rss.channel', 'rss.channel.item', 'rss', 'rss.item']),
3940
'rss.channel.title',
4041
'rss.channel.link',
4142
'rss.channel.description',
@@ -57,16 +58,9 @@ const feedStopNodes = [
5758
...sharedStopNodes.map((node) => `rss.${node}`),
5859
]
5960

60-
const stopNodeExpressions = createStopNodeExpressions(
61-
namespaceStopNodes,
62-
feedStopNodes,
63-
['rss.channel', 'rss.channel.item', 'rss', 'rss.item'],
64-
namespaceContainers,
65-
)
66-
6761
export const parser = new XMLParser({
6862
...parserConfig,
69-
stopNodes: stopNodeExpressions,
63+
stopNodes: stopNodes.map((node) => new Expression(node)),
7064
})
7165

7266
export const normalizeNamespaces = createNamespaceNormalizator(namespaceUris, namespacePrefixes)

src/opml/parse/config.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ export const stopNodes = [
2323
// '*.outline.outline',
2424
]
2525

26-
// Pre-construct Expression objects once at module load to avoid re-parsing
27-
// stop node strings on every XMLParser.parse() call.
28-
const stopNodeExpressions = stopNodes.map((node) => new Expression(node))
29-
3026
export const parser = new XMLParser({
3127
...parserConfig,
32-
stopNodes: stopNodeExpressions,
28+
stopNodes: stopNodes.map((node) => new Expression(node)),
3329
})

0 commit comments

Comments
 (0)