Skip to content

Commit 09598ee

Browse files
Merge branch 'main' into beta
# Conflicts: # bun.lock # package.json
2 parents b8c6780 + 09986be commit 09598ee

8 files changed

Lines changed: 146 additions & 95 deletions

File tree

bun.lock

Lines changed: 68 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@
5454
"docs:build": "vitepress build docs"
5555
},
5656
"dependencies": {
57-
"entities": "^8.0.0",
58-
"fast-xml-parser": "~5.4.2"
57+
"entities": "^7.0.1",
58+
"fast-xml-parser": "~5.5.12"
5959
},
6060
"devDependencies": {
61-
"@types/bun": "^1.3.11",
61+
"@types/bun": "^1.3.12",
6262
"kvalita": "^1.12.4",
63-
"tsdown": "^0.21.7",
63+
"tsdown": "^0.21.8",
6464
"vitepress": "^2.0.0-alpha.17"
6565
}
6666
}

src/common/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const parserConfig: X2jOptions = {
107107
ignorePiTags: true,
108108
ignoreDeclaration: true,
109109
attributeNamePrefix: '@',
110+
jPath: false,
110111
transformTagName: (name) => name.toLowerCase(),
111112
transformAttributeName: (name) => name.toLowerCase(),
112113
}

src/common/utils.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,3 +887,46 @@ export const parseJsonObject = (value: unknown): unknown => {
887887
return JSON.parse(value)
888888
} catch {}
889889
}
890+
891+
// TODO: Remove expandStopNodes and namespaceContainers after fast-xml-parser
892+
// resolves stop node performance regression. Revert feed configs from
893+
// ...expandStopNodes(namespaceStopNodes, [...]) back to ...namespaceStopNodes.
894+
// https://github.com/NaturalIntelligence/fast-xml-parser/issues/816
895+
896+
// Namespace elements that act as containers for other namespace elements.
897+
const namespaceContainers = [
898+
'media:group',
899+
'media:content',
900+
'media:group.media:content',
901+
'media:embed',
902+
'podcast:liveitem',
903+
'itunes:owner',
904+
]
905+
906+
// Expands wildcard stop nodes (*.prefix:element) into explicit paths for
907+
// each feed container, including paths through namespace containers (e.g.
908+
// media:group, podcast:liveitem). Only crosses containers with leaves from
909+
// the same namespace prefix.
910+
export const expandStopNodes = (
911+
stopNodes: Array<string>,
912+
feedContainerPaths: Array<string>,
913+
): Array<string> => {
914+
const set = new Set<string>()
915+
916+
for (const node of stopNodes) {
917+
const suffix = node.slice(2)
918+
const prefix = suffix.split(':')[0]
919+
920+
for (const feedPath of feedContainerPaths) {
921+
set.add(`${feedPath}.${suffix}`)
922+
923+
for (const nsContainer of namespaceContainers) {
924+
if (nsContainer.split(':')[0] === prefix) {
925+
set.add(`${feedPath}.${nsContainer}.${suffix}`)
926+
}
927+
}
928+
}
929+
}
930+
931+
return [...set]
932+
}

src/feeds/atom/parse/config.ts

Lines changed: 4 additions & 3 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 {
34
namespacePrefixes,
45
namespaceStopNodes,
56
namespaceUris,
67
parserConfig,
78
} from '../../../common/config.js'
8-
import { createNamespaceNormalizator } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
910

1011
export const stopNodes = [
11-
...namespaceStopNodes,
12+
...expandStopNodes(namespaceStopNodes, ['feed', 'feed.entry', 'feed.entry.source']),
1213
'feed.author.name',
1314
'feed.author.uri',
1415
'feed.author.url', // Atom 0.3.
@@ -72,7 +73,7 @@ export const stopNodes = [
7273

7374
export const parser = new XMLParser({
7475
...parserConfig,
75-
stopNodes,
76+
stopNodes: stopNodes.map((node) => new Expression(node)),
7677
})
7778

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

src/feeds/rdf/parse/config.ts

Lines changed: 9 additions & 3 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 {
34
namespacePrefixes,
45
namespaceStopNodes,
56
namespaceUris,
67
parserConfig,
78
} from '../../../common/config.js'
8-
import { createNamespaceNormalizator } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
910

1011
export const stopNodes = [
11-
...namespaceStopNodes,
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',
@@ -26,7 +32,7 @@ export const stopNodes = [
2632

2733
export const parser = new XMLParser({
2834
...parserConfig,
29-
stopNodes,
35+
stopNodes: stopNodes.map((node) => new Expression(node)),
3036
})
3137

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

src/feeds/rss/parse/config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { XMLParser } from 'fast-xml-parser'
2+
import { Expression } from 'path-expression-matcher'
23
import {
34
namespacePrefixes,
45
namespaceStopNodes,
56
namespaceUris,
67
parserConfig,
78
} from '../../../common/config.js'
8-
import { createNamespaceNormalizator } from '../../../common/utils.js'
9+
import { createNamespaceNormalizator, expandStopNodes } from '../../../common/utils.js'
910

1011
// These elements can appear both inside <channel> and as direct children of
1112
// <rss> in malformed feeds, so stop nodes are generated for both paths.
@@ -35,7 +36,7 @@ const sharedStopNodes = [
3536
]
3637

3738
export const stopNodes = [
38-
...namespaceStopNodes,
39+
...expandStopNodes(namespaceStopNodes, ['rss.channel', 'rss.channel.item', 'rss', 'rss.item']),
3940
'rss.channel.title',
4041
'rss.channel.link',
4142
'rss.channel.description',
@@ -59,7 +60,7 @@ export const stopNodes = [
5960

6061
export const parser = new XMLParser({
6162
...parserConfig,
62-
stopNodes,
63+
stopNodes: stopNodes.map((node) => new Expression(node)),
6364
})
6465

6566
export const normalizeNamespaces = createNamespaceNormalizator(namespaceUris, namespacePrefixes)

src/opml/parse/config.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import { XMLParser } from 'fast-xml-parser'
2+
import { Expression } from 'path-expression-matcher'
23
import { parserConfig } from '../../common/config.js'
34

45
export const stopNodes = [
56
'opml.head.title',
6-
'opml.head.dateCreated',
7-
'opml.head.dateModified',
8-
'opml.head.ownerName',
9-
'opml.head.ownerEmail',
10-
'opml.head.ownerId',
7+
'opml.head.datecreated',
8+
'opml.head.datemodified',
9+
'opml.head.ownername',
10+
'opml.head.owneremail',
11+
'opml.head.ownerid',
1112
'opml.head.docs',
12-
'opml.head.expansionState',
13-
'opml.head.vertScrollState',
14-
'opml.head.windowTop',
15-
'opml.head.windowLeft',
16-
'opml.head.windowBottom',
17-
'opml.head.windowRight',
13+
'opml.head.expansionstate',
14+
'opml.head.vertscrollstate',
15+
'opml.head.windowtop',
16+
'opml.head.windowleft',
17+
'opml.head.windowbottom',
18+
'opml.head.windowright',
1819
// Not a stop node because it supports recursive nesting that requires parser traversal.
1920
// '*.outline',
2021
// Not a stop node because *.X.Y wildcard patterns don't work in fast-xml-parser
@@ -24,5 +25,5 @@ export const stopNodes = [
2425

2526
export const parser = new XMLParser({
2627
...parserConfig,
27-
stopNodes,
28+
stopNodes: stopNodes.map((node) => new Expression(node)),
2829
})

0 commit comments

Comments
 (0)