Repository: FontoXML/fontoxpath
Pull request #632 (2024-04-02 12:30:38)

Bump ip from 2.0.0 to 2.0.1 in /demo
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/indutny/node-ip/commit/3b0994a74eca51df01f08c40d6a65ba0e1845d04"><code>3b0994a</code></a> 2.0.1</li>
<li><a href="https://github.com/indutny/node-ip/commit/32f468f1245574785ec080705737a579be1223aa"><code>32f468f</code></a> lib: fixed CVE-2023-42282 and added unit test</li>
<li>See full diff in <a href="https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ip&package-manager=npm_and_yarn&previous-version=2.0.0&new-version=2.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/FontoXML/fontoxpath/network/alerts).

</details>

bundlemon[bot]: <!-- bundlemon -->
## BundleMon

  
  
  
  
  
  
  
<details>
<summary>Unchanged files (2)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | fontoxpath.esm.js<br/> | 78.18KB | -
:white_check_mark: | fontoxpath.js<br/> | 78.06KB | -
</details>



No change in files bundle size

Final result: :white_check_mark:

[View report in BundleMon website ➡️](https://app.bundlemon.dev/projects/6437c8f89a7cb205c434db8d/reports/660bfb8a28966cf6985fac46)

---
<p align="center"><a href="https://app.bundlemon.dev/projects/6437c8f89a7cb205c434db8d/reports?branch=dependabot%2Fnpm_and_yarn%2Fdemo%2Fip-2.0.1&resolution=all" target="_blank" rel="noreferrer noopener">Current branch size history</a> | <a href="https://app.bundlemon.dev/projects/6437c8f89a7cb205c434db8d/reports?branch=master&resolution=days" target="_blank" rel="noreferrer noopener">Target branch size history</a></p>
coveralls: 
[![Coverage Status](https://coveralls.io/builds/66661730/badge)](https://coveralls.io/builds/66661730)

coverage: 91.594%. remained the same
when pulling **7fc26058be7776e0cd4ad37ee13aaf6a152ab323 on dependabot/npm_and_yarn/demo/ip-2.0.1**
into **b3e36f89a070e9b8ac0360c3ef2c1febac071093 on master**.


Patch:
diff --git a/LICENSE.md b/LICENSE.md
index 6c89137c..fd39b98f 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2017 Fonto Group BV
+Copyright (c) 2017 Fonto Group BV, Martin Middel
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/demo/package-lock.json b/demo/package-lock.json
index 87ac88f0..20f4f387 100644
--- a/demo/package-lock.json
+++ b/demo/package-lock.json
@@ -1107,9 +1107,9 @@
 			"dev": true
 		},
 		"node_modules/ip": {
-			"version": "2.0.0",
-			"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
-			"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
+			"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
 			"dev": true
 		},
 		"node_modules/is-arrayish": {
@@ -3642,9 +3642,9 @@
 			"dev": true
 		},
 		"ip": {
-			"version": "2.0.0",
-			"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
-			"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
+			"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
 			"dev": true
 		},
 		"is-arrayish": {
diff --git a/package-lock.json b/package-lock.json
index 3adc5ab4..053e2f90 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
 	"name": "fontoxpath",
-	"version": "3.30.3",
+	"version": "3.32.0",
 	"lockfileVersion": 2,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "fontoxpath",
-			"version": "3.30.3",
+			"version": "3.32.0",
 			"license": "MIT",
 			"dependencies": {
 				"prsc": "4.0.0",
@@ -212,12 +212,12 @@
 			}
 		},
 		"node_modules/@babel/generator": {
-			"version": "7.22.15",
-			"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz",
-			"integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
+			"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
 			"dev": true,
 			"dependencies": {
-				"@babel/types": "^7.22.15",
+				"@babel/types": "^7.23.0",
 				"@jridgewell/gen-mapping": "^0.3.2",
 				"@jridgewell/trace-mapping": "^0.3.17",
 				"jsesc": "^2.5.1"
@@ -273,13 +273,13 @@
 			}
 		},
 		"node_modules/@babel/helper-function-name": {
-			"version": "7.22.5",
-			"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
-			"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+			"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
 			"dev": true,
 			"dependencies": {
-				"@babel/template": "^7.22.5",
-				"@babel/types": "^7.22.5"
+				"@babel/template": "^7.22.15",
+				"@babel/types": "^7.23.0"
 			},
 			"engines": {
 				"node": ">=6.9.0"
@@ -479,9 +479,9 @@
 			}
 		},
 		"node_modules/@babel/parser": {
-			"version": "7.22.16",
-			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
-			"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+			"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
 			"dev": true,
 			"bin": {
 				"parser": "bin/babel-parser.js"
@@ -517,19 +517,19 @@
 			}
 		},
 		"node_modules/@babel/traverse": {
-			"version": "7.22.20",
-			"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz",
-			"integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==",
+			"version": "7.23.2",
+			"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
+			"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
 			"dev": true,
 			"dependencies": {
 				"@babel/code-frame": "^7.22.13",
-				"@babel/generator": "^7.22.15",
+				"@babel/generator": "^7.23.0",
 				"@babel/helper-environment-visitor": "^7.22.20",
-				"@babel/helper-function-name": "^7.22.5",
+				"@babel/helper-function-name": "^7.23.0",
 				"@babel/helper-hoist-variables": "^7.22.5",
 				"@babel/helper-split-export-declaration": "^7.22.6",
-				"@babel/parser": "^7.22.16",
-				"@babel/types": "^7.22.19",
+				"@babel/parser": "^7.23.0",
+				"@babel/types": "^7.23.0",
 				"debug": "^4.1.0",
 				"globals": "^11.1.0"
 			},
@@ -547,13 +547,13 @@
 			}
 		},
 		"node_modules/@babel/types": {
-			"version": "7.22.19",
-			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz",
-			"integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+			"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
 			"dev": true,
 			"dependencies": {
 				"@babel/helper-string-parser": "^7.22.5",
-				"@babel/helper-validator-identifier": "^7.22.19",
+				"@babel/helper-validator-identifier": "^7.22.20",
 				"to-fast-properties": "^2.0.0"
 			},
 			"engines": {
@@ -2443,12 +2443,12 @@
 			"dev": true
 		},
 		"node_modules/axios": {
-			"version": "1.5.0",
-			"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
-			"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+			"version": "1.6.8",
+			"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+			"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
 			"dev": true,
 			"dependencies": {
-				"follow-redirects": "^1.15.0",
+				"follow-redirects": "^1.15.6",
 				"form-data": "^4.0.0",
 				"proxy-from-env": "^1.1.0"
 			}
@@ -2618,12 +2618,12 @@
 			}
 		},
 		"node_modules/braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
 			"dev": true,
 			"dependencies": {
-				"fill-range": "^7.0.1"
+				"fill-range": "^7.1.1"
 			},
 			"engines": {
 				"node": ">=8"
@@ -3175,9 +3175,9 @@
 			"dev": true
 		},
 		"node_modules/concurrently": {
-			"version": "8.2.1",
-			"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.1.tgz",
-			"integrity": "sha512-nVraf3aXOpIcNud5pB9M82p1tynmZkrSGQ1p6X/VY8cJ+2LMVqAgXsJxYYefACSHbTYlm92O1xuhdGTjwoEvbQ==",
+			"version": "8.2.2",
+			"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
+			"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
 			"dev": true,
 			"dependencies": {
 				"chalk": "^4.1.2",
@@ -4507,9 +4507,9 @@
 			}
 		},
 		"node_modules/fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
 			"dev": true,
 			"dependencies": {
 				"to-regex-range": "^5.0.1"
@@ -4672,9 +4672,9 @@
 			}
 		},
 		"node_modules/follow-redirects": {
-			"version": "1.15.4",
-			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
-			"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+			"version": "1.15.6",
+			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+			"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
 			"dev": true,
 			"funding": [
 				{
@@ -10690,12 +10690,12 @@
 			}
 		},
 		"@babel/generator": {
-			"version": "7.22.15",
-			"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz",
-			"integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
+			"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
 			"dev": true,
 			"requires": {
-				"@babel/types": "^7.22.15",
+				"@babel/types": "^7.23.0",
 				"@jridgewell/gen-mapping": "^0.3.2",
 				"@jridgewell/trace-mapping": "^0.3.17",
 				"jsesc": "^2.5.1"
@@ -10737,13 +10737,13 @@
 			"dev": true
 		},
 		"@babel/helper-function-name": {
-			"version": "7.22.5",
-			"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
-			"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+			"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
 			"dev": true,
 			"requires": {
-				"@babel/template": "^7.22.5",
-				"@babel/types": "^7.22.5"
+				"@babel/template": "^7.22.15",
+				"@babel/types": "^7.23.0"
 			}
 		},
 		"@babel/helper-hoist-variables": {
@@ -10894,9 +10894,9 @@
 			}
 		},
 		"@babel/parser": {
-			"version": "7.22.16",
-			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
-			"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+			"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
 			"dev": true
 		},
 		"@babel/runtime": {
@@ -10920,19 +10920,19 @@
 			}
 		},
 		"@babel/traverse": {
-			"version": "7.22.20",
-			"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz",
-			"integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==",
+			"version": "7.23.2",
+			"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
+			"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
 			"dev": true,
 			"requires": {
 				"@babel/code-frame": "^7.22.13",
-				"@babel/generator": "^7.22.15",
+				"@babel/generator": "^7.23.0",
 				"@babel/helper-environment-visitor": "^7.22.20",
-				"@babel/helper-function-name": "^7.22.5",
+				"@babel/helper-function-name": "^7.23.0",
 				"@babel/helper-hoist-variables": "^7.22.5",
 				"@babel/helper-split-export-declaration": "^7.22.6",
-				"@babel/parser": "^7.22.16",
-				"@babel/types": "^7.22.19",
+				"@babel/parser": "^7.23.0",
+				"@babel/types": "^7.23.0",
 				"debug": "^4.1.0",
 				"globals": "^11.1.0"
 			},
@@ -10946,13 +10946,13 @@
 			}
 		},
 		"@babel/types": {
-			"version": "7.22.19",
-			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz",
-			"integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==",
+			"version": "7.23.0",
+			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+			"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
 			"dev": true,
 			"requires": {
 				"@babel/helper-string-parser": "^7.22.5",
-				"@babel/helper-validator-identifier": "^7.22.19",
+				"@babel/helper-validator-identifier": "^7.22.20",
 				"to-fast-properties": "^2.0.0"
 			}
 		},
@@ -12340,12 +12340,12 @@
 			"dev": true
 		},
 		"axios": {
-			"version": "1.5.0",
-			"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
-			"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+			"version": "1.6.8",
+			"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+			"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
 			"dev": true,
 			"requires": {
-				"follow-redirects": "^1.15.0",
+				"follow-redirects": "^1.15.6",
 				"form-data": "^4.0.0",
 				"proxy-from-env": "^1.1.0"
 			}
@@ -12483,12 +12483,12 @@
 			}
 		},
 		"braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
 			"dev": true,
 			"requires": {
-				"fill-range": "^7.0.1"
+				"fill-range": "^7.1.1"
 			}
 		},
 		"brotli-size": {
@@ -12870,9 +12870,9 @@
 			"dev": true
 		},
 		"concurrently": {
-			"version": "8.2.1",
-			"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.1.tgz",
-			"integrity": "sha512-nVraf3aXOpIcNud5pB9M82p1tynmZkrSGQ1p6X/VY8cJ+2LMVqAgXsJxYYefACSHbTYlm92O1xuhdGTjwoEvbQ==",
+			"version": "8.2.2",
+			"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
+			"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
 			"dev": true,
 			"requires": {
 				"chalk": "^4.1.2",
@@ -13903,9 +13903,9 @@
 			}
 		},
 		"fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
 			"dev": true,
 			"requires": {
 				"to-regex-range": "^5.0.1"
@@ -14045,9 +14045,9 @@
 			}
 		},
 		"follow-redirects": {
-			"version": "1.15.4",
-			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
-			"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+			"version": "1.15.6",
+			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+			"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
 			"dev": true
 		},
 		"for-each": {
diff --git a/package.json b/package.json
index b16373e9..0005dd57 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "fontoxpath",
-	"version": "3.30.3",
+	"version": "3.32.0",
 	"description": "A minimalistic XPath 3.1 engine in JavaScript",
 	"main": "dist/fontoxpath.js",
 	"module": "dist/fontoxpath.esm.js",
diff --git a/src/domFacade/ConcreteNode.ts b/src/domFacade/ConcreteNode.ts
index 8f84393e..7ed11174 100644
--- a/src/domFacade/ConcreteNode.ts
+++ b/src/domFacade/ConcreteNode.ts
@@ -28,6 +28,9 @@ export type ConcreteCDATASectionNode = CDATASection & { nodeType: NODE_TYPES.CDA
 export type ConcreteDocumentTypeNode = DocumentType & { nodeType: NODE_TYPES.DOCUMENT_TYPE_NODE };
 export type ConcreteAttributeNode = Attr & { nodeType: NODE_TYPES.ATTRIBUTE_NODE };
 export type ConcreteDocumentNode = Document & { nodeType: NODE_TYPES.DOCUMENT_NODE };
+export type ConcreteDocumentFragmentNode = DocumentFragment & {
+	nodeType: NODE_TYPES.DOCUMENT_FRAGMENT_NODE;
+};
 export type ConcreteProcessingInstructionNode = ProcessingInstruction & {
 	nodeType: NODE_TYPES.PROCESSING_INSTRUCTION_NODE;
 };
@@ -47,4 +50,5 @@ export type ConcreteNode =
 	| ConcreteChildNode
 	| ConcreteParentNode
 	| ConcreteAttributeNode
-	| ConcreteDocumentTypeNode;
+	| ConcreteDocumentTypeNode
+	| ConcreteDocumentFragmentNode;
diff --git a/src/expressions/dataTypes/atomize.ts b/src/expressions/dataTypes/atomize.ts
index b5bb53c3..77ecbdb2 100644
--- a/src/expressions/dataTypes/atomize.ts
+++ b/src/expressions/dataTypes/atomize.ts
@@ -79,7 +79,11 @@ export function atomizeSingleValue(
 				);
 				return;
 			}
-			if (aNodeType === NODE_TYPES.ELEMENT_NODE || aNodeType === NODE_TYPES.DOCUMENT_NODE) {
+			if (
+				aNodeType === NODE_TYPES.ELEMENT_NODE ||
+				aNodeType === NODE_TYPES.DOCUMENT_NODE ||
+				aNodeType === NODE_TYPES.DOCUMENT_FRAGMENT_NODE
+			) {
 				const children = domFacade.getChildNodes(
 					aNode as ConcreteParentNode | TinyParentNode,
 				);
diff --git a/src/expressions/functions/builtInFunctions_node.ts b/src/expressions/functions/builtInFunctions_node.ts
index 23235f0c..42006831 100644
--- a/src/expressions/functions/builtInFunctions_node.ts
+++ b/src/expressions/functions/builtInFunctions_node.ts
@@ -30,7 +30,7 @@ import { errXPDY0002 } from '../XPathErrors';
 import { BuiltinDeclarationType } from './builtInFunctions';
 import builtinStringFunctions from './builtInFunctions_string';
 import FunctionDefinitionType from './FunctionDefinitionType';
-
+import generateId from './generateId';
 const fnString = builtinStringFunctions.functions.string;
 
 function contextItemAsFirstArgument(
@@ -123,6 +123,23 @@ const fnData: FunctionDefinitionType = (
 	return atomize(sequence, executionParameters);
 };
 
+const fnGenerateId: FunctionDefinitionType = (
+	dynamicContext,
+	executionParameters,
+	_staticContext,
+	nodeValue,
+) => {
+	if (nodeValue.isEmpty()) {
+		return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
+	}
+	if (!isSubtypeOf(nodeValue.first().type, ValueType.NODE)) {
+		throw new Error('XPTY0004: The context item must be a node.');
+	}
+	const node = nodeValue.first().value as NodePointer;
+
+	return sequenceFactory.singleton(createAtomicValue(generateId(node), ValueType.XSSTRING));
+};
+
 const fnHasChildren: FunctionDefinitionType = (
 	_dynamicContext,
 	executionParameters,
@@ -165,6 +182,78 @@ function areSameNode(
 	return true;
 }
 
+/**
+ * Test if a string should match the xml:lang attribute string of a node.
+ */
+function langMatchString(truth: string, test: string): boolean {
+	truth = truth.toLowerCase();
+	test = test.toLowerCase();
+	if (truth === test) {
+		return true;
+	} else {
+		return truth.length < 5 || !truth.startsWith(test)
+			? false
+			: /* Strip the least specific part and test again */
+			  langMatchString(truth.replace(/-[a-z0-9]+$/, ''), test);
+	}
+}
+
+/**
+ * Starting at a given node, test the xml:lang attribute to see if it
+   matches a test. Move up the tree until the test passes or fails.
+ */
+function langMatchNode(node: NodePointer, domFacade: DomFacade, test: string): ISequence {
+	let toTest = node;
+	while (toTest) {
+		if (domFacade.getNodeType(toTest) !== NODE_TYPES.ELEMENT_NODE) {
+			toTest = domFacade.getParentNodePointer(toTest as AttributeNodePointer);
+		} else if (domFacade.getNodeType(toTest) === NODE_TYPES.ELEMENT_NODE) {
+			const attrValue = domFacade.getAttribute(toTest as ElementNodePointer, 'xml:lang');
+			if (attrValue) {
+				return langMatchString(attrValue, test)
+					? sequenceFactory.singletonTrueSequence()
+					: sequenceFactory.singletonFalseSequence();
+			} else {
+				toTest = domFacade.getParentNodePointer(toTest as ElementNodePointer);
+			}
+		}
+	}
+	return sequenceFactory.singletonFalseSequence();
+}
+
+const fnLang: FunctionDefinitionType = (
+	dynamicContext,
+	executionParameters,
+	_staticContext,
+	langTestValue,
+	nodeValue,
+) => {
+	const domFacade = executionParameters.domFacade;
+	let langTest;
+	if (langTestValue.isEmpty()) {
+		langTest = '';
+	} else if (!isSubtypeOf(langTestValue.first().type, ValueType.XSSTRING)) {
+		throw new Error('XPTY0004: The first argument of lang must be a string.');
+	} else {
+		langTest = langTestValue.first().value;
+	}
+	let node;
+	if (!nodeValue) {
+		if (!dynamicContext || !dynamicContext.contextItem) {
+			throw errXPDY0002(
+				`The function lang depends on dynamic context if a node is not passed as the second argument.`,
+			);
+		}
+		if (!isSubtypeOf(dynamicContext.contextItem.type, ValueType.NODE)) {
+			throw new Error('XPTY0004: The context item must be a node.');
+		}
+		node = dynamicContext.contextItem.value;
+	} else {
+		node = nodeValue.first().value;
+	}
+	return langMatchNode(node, domFacade, langTest);
+};
+
 const fnPath: FunctionDefinitionType = (
 	_dynamicContext,
 	executionParameters,
@@ -545,6 +634,38 @@ const declarations: BuiltinDeclarationType[] = [
 		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
 		returnType: { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_MORE },
 	},
+
+	{
+		argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
+		callFunction: fnLang,
+		localName: 'lang',
+		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
+		returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
+	},
+	{
+		argumentTypes: [
+			{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
+			{ type: ValueType.NODE, mult: SequenceMultiplicity.EXACTLY_ONE },
+		],
+		callFunction: fnLang,
+		localName: 'lang',
+		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
+		returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
+	},
+	{
+		argumentTypes: [],
+		callFunction: contextItemAsFirstArgument.bind(null, 'generate-id', fnGenerateId),
+		localName: 'generate-id',
+		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
+		returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
+	},
+	{
+		argumentTypes: [{ type: ValueType.NODE, mult: SequenceMultiplicity.ZERO_OR_ONE }],
+		callFunction: fnGenerateId,
+		localName: 'generate-id',
+		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
+		returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
+	},
 ];
 
 export default {
diff --git a/src/expressions/functions/builtInFunctions_string.ts b/src/expressions/functions/builtInFunctions_string.ts
index f347b16d..291c775b 100644
--- a/src/expressions/functions/builtInFunctions_string.ts
+++ b/src/expressions/functions/builtInFunctions_string.ts
@@ -355,11 +355,25 @@ const fnTokenize: FunctionDefinitionType = (
 	const patternString: string = pattern.first().value;
 
 	const regex = compileJSRegex(patternString);
+	regex.lastIndex = 0;
+	const results = [];
+	let result = regex.exec(inputString);
+
+	let index = 0;
+
+	// Repeatedly execute the regex to get all the outputs
+	// TODO: with this approach it should not be too difficult to actually use XSPattern here.
+	while (result) {
+		results.push(inputString.slice(index, result.index));
+		index = regex.lastIndex;
+		result = regex.exec(inputString);
+	}
+
+	// Don't forget the last split part!
+	results.push(inputString.slice(index));
 
 	return sequenceFactory.create(
-		inputString
-			.split(regex)
-			.map((token: string) => createAtomicValue(token, ValueType.XSSTRING)),
+		results.map((token: string) => createAtomicValue(token, ValueType.XSSTRING)),
 	);
 };
 
diff --git a/src/expressions/functions/generateId.ts b/src/expressions/functions/generateId.ts
new file mode 100644
index 00000000..c92b2344
--- /dev/null
+++ b/src/expressions/functions/generateId.ts
@@ -0,0 +1,15 @@
+import { ConcreteNode } from '../../domFacade/ConcreteNode';
+import { NodePointer, TinyNode } from '../../domClone/Pointer';
+
+const generateIdMap: WeakMap<TinyNode | ConcreteNode, string> = new WeakMap();
+let generateIdCounter = 0;
+
+function generateId(ptr: NodePointer): string {
+	const node = ptr.node;
+	if (!generateIdMap.has(node)) {
+		generateIdMap.set(node, `id${++generateIdCounter}`);
+	}
+	return generateIdMap.get(node);
+}
+
+export default generateId;


Test patch:
diff --git a/test/assets/runnableTestSets.csv b/test/assets/runnableTestSets.csv
index 9b8ff536..f864b73d 100644
--- a/test/assets/runnableTestSets.csv
+++ b/test/assets/runnableTestSets.csv
@@ -111,7 +111,7 @@ fn-insert-before,true
 fn-iri-to-uri,true
 fn-json-doc,false
 fn-json-to-xml,false
-fn-lang,false
+fn-lang,true
 fn-last,true
 fn-load-xquery-module,false
 fn-local-name,false
diff --git a/test/assets/unrunnableTestCases.csv b/test/assets/unrunnableTestCases.csv
index da65a04c..79a5f487 100644
--- a/test/assets/unrunnableTestCases.csv
+++ b/test/assets/unrunnableTestCases.csv
@@ -607,10 +607,6 @@ fn-function-lookup-259,AssertionError: Expected XPath exists(function-lookup(fn:
 fn-function-lookup-260,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-261,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'in-scope-prefixes'), 1)) to resolve to true: expected false to be true
 fn-function-lookup-262,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
-fn-function-lookup-275,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'lang'), 1)) to resolve to true: expected false to be true
-fn-function-lookup-276,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
-fn-function-lookup-277,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'lang'), 2)) to resolve to true: expected false to be true
-fn-function-lookup-278,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-318,Error: FOCH0002: No collations are supported
 fn-function-lookup-320,Error: XPTY0004: eqOp not available for xs:integer and xs:string
 fn-function-lookup-322,Error: FOCH0002: No collations are supported
@@ -621,10 +617,6 @@ fn-function-lookup-353,AssertionError: Expected XPath exists(function-lookup(fn:
 fn-function-lookup-354,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-355,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'element-with-id'), 2)) to resolve to true: expected false to be true
 fn-function-lookup-356,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
-fn-function-lookup-361,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'generate-id'), 0)) to resolve to true: expected false to be true
-fn-function-lookup-362,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
-fn-function-lookup-363,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'generate-id'), 1)) to resolve to true: expected false to be true
-fn-function-lookup-364,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-365,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'doc'), 1)) to resolve to true: expected false to be true
 fn-function-lookup-366,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-367,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'doc-available'), 1)) to resolve to true: expected false to be true
@@ -727,33 +719,13 @@ fn-function-name-014,AssertionError: Expected executing the XPath "fn:function-n
 fn-function-name-018,AssertionError: Expected executing the XPath "fn:function-name( concat#340282366920938463463374607431768211456 )" to resolve to one of the expected results, but got AssertionError: expected [Function] to throw error matching /FOAR0002/ but got 'Invalid array length', RangeError: Invalid array length.
 fn-function-name-023,Error: 1: ( 2: 	declare function local:add($arg1, $arg2, $arg3)    	        ^ 3:         { 4:            $arg1 + $arg2 + $arg3  Error: XPST0003: Failed to parse script. Expected -,+,validate,(#,(,#,parent::,ancestor::,preceding-sibling::,preceding::,ancestor-or-self::,..,child::,descendant::,attribute::,self::,descendant-or-self::,following-sibling::,following::,document-node(,element,attribute,schema-element,schema-attribute(,processing-instruction(,processing-instruction(),comment(),text(),namespace-node(),node(),:*,Q,[A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD],[\uD800-\uDB7F],/,//   at <>:2:10 - 2:11
 fn-function-name-024,Error: No selector counterpart for: typedFunctionTest.
-generate-id-000,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-001,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-002,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-003,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-004,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-005,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-006,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-008,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
-generate-id-009,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
-generate-id-010,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
 generate-id-012,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}collection with arity of 0 not registered. No similar functions found.
 generate-id-013,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}collection with arity of 0 not registered. No similar functions found.
 generate-id-014,Error: No selector counterpart for: computedDocumentConstructor.
 generate-id-015,Error: No selector counterpart for: computedDocumentConstructor.
 generate-id-016,Error: No selector counterpart for: computedDocumentConstructor.
 generate-id-017,Error: No selector counterpart for: computedDocumentConstructor.
-generate-id-018,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-019,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
-generate-id-020,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-021,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-022,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-023,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-generate-id-901,AssertionError: expected [Function] to throw error matching /XPDY0002/ but got 'XPST0017: Function Q{http://www.w3.or…'
-generate-id-902,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function Q{http://www.w3.or…'
-generate-id-903,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function Q{http://www.w3.or…'
-generate-id-904,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function Q{http://www.w3.or…'
-generate-id-905,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function Q{http://www.w3.or…'
+generate-id-018,Error: XPTY0004: Multiplicity of function argument of type node()? for generate-id is incorrect. Expected "?", but got "+".
 fn-has-children-007,AssertionError: Expected executing the XPath "(1)[fn:has-children()]" to resolve to one of the expected results, but got AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…'.
 fn-has-children-008,AssertionError: Expected executing the XPath "(fn:concat#2)[fn:has-children()]" to resolve to one of the expected results, but got AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'Cannot read properties of undefined (…'.
 fn-has-children-012,AssertionError: expected [Function] to throw an error
@@ -804,6 +776,8 @@ K-SeqInsertBeforeFunc-17,Error: No selector counterpart for: treatExpr.
 K-SeqInsertBeforeFunc-18,Error: No selector counterpart for: treatExpr.
 K-SeqInsertBeforeFunc-19,Error: No selector counterpart for: treatExpr.
 K-SeqInsertBeforeFunc-20,Error: No selector counterpart for: treatExpr.
+fn-lang-31,AssertionError: Expected XPath let $l := /langs/para[4]!fn:lang#1 return /langs/para[1]!$l('en') to resolve to false: expected true to be false
+fn-lang-32,AssertionError: Expected XPath let $l := /langs/para[4]!fn:lang#1 return /langs/para[1]!$l('de') to resolve to true: expected false to be true
 K-ContextLastFunc-5,Error: No selector counterpart for: treatExpr.
 K-ContextLastFunc-24,Error: No selector counterpart for: treatExpr.
 K-ContextLastFunc-25,Error: No selector counterpart for: treatExpr.
@@ -1625,7 +1599,6 @@ cbcl-timezone-from-date-007,Error: XPST0017: Function Q{http://www.w3.org/2005/x
 fn-tokenize-2,AssertionError: expected [Function] to throw error matching /FORX0001/ but got 'Not implemented: Using flags in token…'
 fn-tokenize-5,Error: Not implemented: Using flags in tokenize is not supported
 fn-tokenize-6,Error: Not implemented: Using flags in tokenize is not supported
-fn-tokenize-9,AssertionError: string-join(fn:tokenize("abracadabra", "(ab)|(a)"), '#'): expected '#ab##r##a#c##a#d#ab##r##a#' to equal '#r#c#d#r#'
 fn-tokenize-31,Error: Not implemented: Using flags in tokenize is not supported
 fn-tokenize-32,Error: Not implemented: Using flags in tokenize is not supported
 fn-tokenize-35,Error: Not implemented: Using flags in tokenize is not supported
@@ -2984,7 +2957,6 @@ K-FunctionCallExpr-5,AssertionError: expected [Function] to throw error matching
 K-FunctionCallExpr-15a,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}format-number with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}format-integer (xs:integer?, xs:string)"?
 K-FunctionCallExpr-16a,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}format-time with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}current-time ()" or "Q{http://www.w3.org/2005/xpath-functions}format-integer (xs:integer?, xs:string)"?
 K-FunctionCallExpr-17a,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}format-time with arity of 5 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}current-time ()" or "Q{http://www.w3.org/2005/xpath-functions}format-integer (xs:integer?, xs:string)"?
-K-FunctionCallExpr-25a,Error: XPST0017: Function generate-id with arity of 1 not registered. No similar functions found.
 function-call-reserved-function-names-034,AssertionError: expected [Function] to throw error matching /XPST0017/ but got '1: \n2: \tnamespace-node(1)\n   \t   …'
 function-declaration-017,Error: XPST0008, The variable v is not in scope.
 function-declaration-022,AssertionError: expected [Function] to throw error matching /XQST0034/ but got 'XQST0049: The function or variable "Q…'
@@ -3161,10 +3133,6 @@ function-literal-259,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-fu
 function-literal-260,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}namespace-uri-for-prefix with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}namespace-uri-from-QName (xs:QName?)", "Q{http://www.w3.org/2005/xpath-functions}namespace-uri (node())" or "Q{http://www.w3.org/2005/xpath-functions}namespace-uri ()"?
 function-literal-261,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}in-scope-prefixes with arity of 1 not registered. No similar functions found.
 function-literal-262,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}in-scope-prefixes with arity of 1 not registered. No similar functions found.
-function-literal-275,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-function-literal-276,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-function-literal-277,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-function-literal-278,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
 function-literal-318,Error: FOCH0002: No collations are supported
 function-literal-320,Error: XPTY0004: eqOp not available for xs:integer and xs:string
 function-literal-322,Error: FOCH0002: No collations are supported
@@ -3175,10 +3143,6 @@ function-literal-353,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-fu
 function-literal-354,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}element-with-id with arity of 1 not registered. No similar functions found.
 function-literal-355,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}element-with-id with arity of 2 not registered. No similar functions found.
 function-literal-356,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}element-with-id with arity of 2 not registered. No similar functions found.
-function-literal-361,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
-function-literal-362,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 0 not registered. No similar functions found.
-function-literal-363,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
-function-literal-364,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}generate-id with arity of 1 not registered. No similar functions found.
 function-literal-365,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}doc with arity of 1 not registered. No similar functions found.
 function-literal-366,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}doc with arity of 1 not registered. No similar functions found.
 function-literal-367,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}doc-available with arity of 1 not registered. No similar functions found.
@@ -3785,13 +3749,6 @@ functx-fn-escape-html-uri-2,Error: XPST0017: Function Q{http://www.w3.org/2005/x
 functx-fn-escape-html-uri-all,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}escape-html-uri with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}encode-for-uri (xs:string?)"?
 functx-fn-index-of-5,Error: XPTY0004: eqOp not available for xs:integer and xs:string
 functx-fn-index-of-all,Error: XPTY0004: eqOp not available for xs:integer and xs:string
-functx-fn-lang-1,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-2,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-3,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-4,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-5,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-6,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
-functx-fn-lang-all,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
 functx-fn-matches-10,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}matches with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}matches (xs:string?, xs:string)"?
 functx-fn-matches-12,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}matches with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}matches (xs:string?, xs:string)"?
 functx-fn-matches-14,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}matches with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}matches (xs:string?, xs:string)"?
@@ -3839,8 +3796,6 @@ functx-functx-dynamic-path-all,Error: No selector counterpart for: computedDocum
 functx-functx-get-matches-1,AssertionError: declare namespace functx = "http://www.example.com/";                     (:~ : Splits a string into matching and non-matching regions : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches-and-non-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches-and-non-matches ( $string as xs:string? , $regex as xs:string ) as element()* { let $iomf := functx:index-of-match-first($string, $regex) return if (empty($iomf)) then <non-match>{$string}</non-match> else if ($iomf > 1) then (<non-match>{substring($string,1,$iomf - 1)}</non-match>, functx:get-matches-and-non-matches( substring($string,$iomf),$regex)) else let $length := string-length($string) - string-length(functx:replace-first($string, $regex,'')) return (<match>{substring($string,1,$length)}</match>, if (string-length($string) > $length) then functx:get-matches-and-non-matches( substring($string,$length + 1),$regex) else ()) } ;                     (:~ : Return the matching regions of a string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches ( $string as xs:string? , $regex as xs:string ) as xs:string* { functx:get-matches-and-non-matches($string,$regex)/ string(self::match) } ;                     (:~ : The first position of a matching substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-match-first.html : @param $arg the string : @param $pattern the pattern to match :)          declare function functx:index-of-match-first ( $arg as xs:string? , $pattern as xs:string ) as xs:integer? { if (matches($arg,$pattern)) then string-length(tokenize($arg, $pattern)[1]) + 1 else () } ;                     (:~ : Replaces the first match of a pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-first.html : @param $arg the entire string to change : @param $pattern the pattern of characters to replace : @param $replacement the replacement string :)          declare function functx:replace-first ( $arg as xs:string? , $pattern as xs:string , $replacement as xs:string ) as xs:string { replace($arg, concat('(^.*?)', $pattern), concat('$1',$replacement)) } ;                     (functx:get-matches( 'abc123def', '\d+')): expected '123  ' to equal ' 123 '
 functx-functx-get-matches-2,AssertionError: declare namespace functx = "http://www.example.com/";                     (:~ : Splits a string into matching and non-matching regions : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches-and-non-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches-and-non-matches ( $string as xs:string? , $regex as xs:string ) as element()* { let $iomf := functx:index-of-match-first($string, $regex) return if (empty($iomf)) then <non-match>{$string}</non-match> else if ($iomf > 1) then (<non-match>{substring($string,1,$iomf - 1)}</non-match>, functx:get-matches-and-non-matches( substring($string,$iomf),$regex)) else let $length := string-length($string) - string-length(functx:replace-first($string, $regex,'')) return (<match>{substring($string,1,$length)}</match>, if (string-length($string) > $length) then functx:get-matches-and-non-matches( substring($string,$length + 1),$regex) else ()) } ;                     (:~ : Return the matching regions of a string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches ( $string as xs:string? , $regex as xs:string ) as xs:string* { functx:get-matches-and-non-matches($string,$regex)/ string(self::match) } ;                     (:~ : The first position of a matching substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-match-first.html : @param $arg the string : @param $pattern the pattern to match :)          declare function functx:index-of-match-first ( $arg as xs:string? , $pattern as xs:string ) as xs:integer? { if (matches($arg,$pattern)) then string-length(tokenize($arg, $pattern)[1]) + 1 else () } ;                     (:~ : Replaces the first match of a pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-first.html : @param $arg the entire string to change : @param $pattern the pattern of characters to replace : @param $replacement the replacement string :)          declare function functx:replace-first ( $arg as xs:string? , $pattern as xs:string , $replacement as xs:string ) as xs:string { replace($arg, concat('(^.*?)', $pattern), concat('$1',$replacement)) } ;                     (functx:get-matches( 'abc123def', '\d')): expected ' 1 3  2' to equal ' 1 2 3 '
 functx-functx-get-matches-all,AssertionError: declare namespace functx = "http://www.example.com/";                     (:~ : Splits a string into matching and non-matching regions : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches-and-non-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches-and-non-matches ( $string as xs:string? , $regex as xs:string ) as element()* { let $iomf := functx:index-of-match-first($string, $regex) return if (empty($iomf)) then <non-match>{$string}</non-match> else if ($iomf > 1) then (<non-match>{substring($string,1,$iomf - 1)}</non-match>, functx:get-matches-and-non-matches( substring($string,$iomf),$regex)) else let $length := string-length($string) - string-length(functx:replace-first($string, $regex,'')) return (<match>{substring($string,1,$length)}</match>, if (string-length($string) > $length) then functx:get-matches-and-non-matches( substring($string,$length + 1),$regex) else ()) } ;                     (:~ : Return the matching regions of a string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches.html : @param $string the string to split : @param $regex the pattern :)          declare function functx:get-matches ( $string as xs:string? , $regex as xs:string ) as xs:string* { functx:get-matches-and-non-matches($string,$regex)/ string(self::match) } ;                     (:~ : The first position of a matching substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-match-first.html : @param $arg the string : @param $pattern the pattern to match :)          declare function functx:index-of-match-first ( $arg as xs:string? , $pattern as xs:string ) as xs:integer? { if (matches($arg,$pattern)) then string-length(tokenize($arg, $pattern)[1]) + 1 else () } ;                     (:~ : Replaces the first match of a pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-first.html : @param $arg the entire string to change : @param $pattern the pattern of characters to replace : @param $replacement the replacement string :)          declare function functx:replace-first ( $arg as xs:string? , $pattern as xs:string , $replacement as xs:string ) as xs:string { replace($arg, concat('(^.*?)', $pattern), concat('$1',$replacement)) } ;                     (functx:get-matches( 'abc123def', '\d+'), functx:get-matches( 'abc123def', '\d'), functx:get-matches( 'abc123def', '[a-z]{2}')): expected '123    1 3  2 ab  de ' to equal ' 123   1 2 3  ab  de '
-functx-functx-line-count-2,AssertionError: Expected XPath declare namespace functx = "http://www.example.com/";                     (:~ : The number of lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_line-count.html : @param $arg the string to test :)          declare function functx:line-count ( $arg as xs:string? ) as xs:integer { count(functx:lines($arg)) } ;                     (:~ : Split a string into separate lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_lines.html : @param $arg the string to split :)          declare function functx:lines ( $arg as xs:string? ) as xs:string* { tokenize($arg, '(\r\n?|\n\r?)') } ;          let $lines := 'a              value              on              many              lines' return (functx:line-count($lines))        to resolve to 5: expected false to be true
-functx-functx-line-count-all,AssertionError: declare namespace functx = "http://www.example.com/";                     (:~ : The number of lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_line-count.html : @param $arg the string to test :)          declare function functx:line-count ( $arg as xs:string? ) as xs:integer { count(functx:lines($arg)) } ;                     (:~ : Split a string into separate lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_lines.html : @param $arg the string to split :)          declare function functx:lines ( $arg as xs:string? ) as xs:string* { tokenize($arg, '(\r\n?|\n\r?)') } ;          let $lines := 'a                      value                      on                      many                      lines'           return (functx:line-count('a value'), functx:line-count($lines)): expected '1 9' to equal '1 5'
 functx-functx-next-day-1,Error: Not implemented: adding durations to xs:date
 functx-functx-next-day-2,Error: Not implemented: adding durations to xs:date
 functx-functx-next-day-all,Error: Not implemented: adding durations to xs:date
@@ -4131,7 +4086,6 @@ fo-test-fn-idref-001,Error: No selector counterpart for: validateExpr.
 fo-test-fn-idref-002,Error: No selector counterpart for: validateExpr.
 fo-test-fn-serialize-001,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
 fo-test-fn-serialize-002,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
-fo-test-fn-filter-002,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}lang with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
 fo-test-fn-for-each-pair-001,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}for-each-pair with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}for-each-pair (array(*), array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/array}for-each (array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/map}for-each (map(*), item()*)" or "Q{http://www.w3.org/2005/xpath-functions}for-each (item()*, function(*))"?
 fo-test-fn-for-each-pair-002,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}for-each-pair with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}for-each-pair (array(*), array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/array}for-each (array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/map}for-each (map(*), item()*)" or "Q{http://www.w3.org/2005/xpath-functions}for-each (item()*, function(*))"?
 fo-test-fn-for-each-pair-003,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}for-each-pair with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}for-each-pair (array(*), array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/array}for-each (array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/map}for-each (map(*), item()*)" or "Q{http://www.w3.org/2005/xpath-functions}for-each (item()*, function(*))"?
diff --git a/test/specs/parsing/functions/functions.node.tests.ts b/test/specs/parsing/functions/functions.node.tests.ts
index bdd9f877..520a8444 100644
--- a/test/specs/parsing/functions/functions.node.tests.ts
+++ b/test/specs/parsing/functions/functions.node.tests.ts
@@ -16,6 +16,113 @@ beforeEach(() => {
 });
 
 describe('functions over nodes', () => {
+	describe('generate-id()', () => {
+		it('returns the same value for the same node', () => {
+			jsonMlMapper.parse(['someElement'], documentNode);
+			chai.assert.isTrue(
+				evaluateXPathToBoolean('generate-id(/) = generate-id(/)', documentNode),
+			);
+		});
+
+		it('returns the empty string for the empty sequence', () => {
+			jsonMlMapper.parse(['someElement'], documentNode);
+			chai.assert.isTrue(evaluateXPathToBoolean('generate-id(()) = ""', documentNode));
+		});
+
+		it('throws the correct error when the context item is not a node', () => {
+			chai.assert.throws(
+				() => evaluateXPathToBoolean('"not a node" ! generate-id()'),
+				'XPTY0004',
+			);
+		});
+
+		it('returns the same value for subsequent calls', () => {
+			jsonMlMapper.parse(['someElement'], documentNode);
+			const id = evaluateXPathToString('generate-id(/)', documentNode);
+			chai.assert.equal(evaluateXPathToString('generate-id(/)', documentNode), id);
+		});
+	});
+
+	describe('lang()', () => {
+		it('returns true when the node has the right lang', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'en' }], documentNode);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en", .)', documentNode.firstChild),
+				true,
+			);
+		});
+
+		it('returns false when the node has the wrong lang', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'en' }], documentNode);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("es", .)', documentNode.firstChild),
+				false,
+			);
+		});
+
+		it('returns true when the node has the right lang, but capitalized', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'EN' }], documentNode);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en", .)', documentNode.firstChild),
+				true,
+			);
+		});
+
+		it('returns true when the node has the right lang, but more specific', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'en-gb' }], documentNode);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en", .)', documentNode.firstChild),
+				true,
+			);
+		});
+
+		it('returns false when the node has the right lang, but not specific enough', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'en' }], documentNode);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en-gb", .)', documentNode.firstChild),
+				false,
+			);
+		});
+
+		it('returns true for implied context', () => {
+			jsonMlMapper.parse(['someElement', { 'xml:lang': 'en' }], documentNode);
+			chai.assert.equal(evaluateXPathToBoolean('lang("en")', documentNode.firstChild), true);
+		});
+
+		it('returns true when the parent is true', () => {
+			jsonMlMapper.parse(
+				['someElement', { 'xml:lang': 'en' }, ['child', 'Hello']],
+				documentNode,
+			);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en")', documentNode.firstChild.firstChild),
+				true,
+			);
+		});
+
+		it('returns false even if the parent is true', () => {
+			jsonMlMapper.parse(
+				['someElement', { 'xml:lang': 'en' }, ['child', { 'xml:lang': 'de' }, 'Hello']],
+				documentNode,
+			);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en")', documentNode.firstChild.firstChild),
+				false,
+			);
+		});
+
+		it('returns true even if the parent is false', () => {
+			jsonMlMapper.parse(
+				['someElement', { 'xml:lang': 'de' }, ['child', { 'xml:lang': 'en' }, 'Hello']],
+				documentNode,
+			);
+			chai.assert.equal(
+				evaluateXPathToBoolean('lang("en")', documentNode.firstChild.firstChild),
+				true,
+			);
+		});
+	});
+
 	describe('node-name()', () => {
 		it('returns an empty sequence if $arg is an empty sequence', () =>
 			chai.assert.isEmpty(evaluateXPathToStrings('node-name(())', documentNode)));
diff --git a/test/specs/parsing/functions/functions.string.tests.ts b/test/specs/parsing/functions/functions.string.tests.ts
index da3f5f9d..a3135c4a 100644
--- a/test/specs/parsing/functions/functions.string.tests.ts
+++ b/test/specs/parsing/functions/functions.string.tests.ts
@@ -95,6 +95,13 @@ describe('functions over strings', () => {
 			));
 	});
 
+	it('tokenize("abracadabra", "(ab)|(a)") returns ("","r","c","d","r","")', () => {
+		chai.assert.sameOrderedMembers(
+			evaluateXPathToStrings('fn:tokenize("abracadabra", "(ab)|(a)")'),
+			['', 'r', 'c', 'd', 'r', ''],
+		);
+	});
+
 	describe('normalize-space()', () => {
 		it('Returns the value of $arg with whitespace normalized by stripping leading and trailing whitespace and replacing sequences of one or more than one whitespace character with a single space, #x20.', () =>
 			chai.assert.equal(
@@ -223,6 +230,19 @@ describe('functions over strings', () => {
 			chai.assert.equal(evaluateXPathToString('string()', document), 'Some <CDATA>');
 		});
 
+		it('regards documentFragment nodes as normal parents', () => {
+			const document = new slimdom.Document();
+			const f = document.createDocumentFragment();
+			const foo = f.appendChild(document.createElement('foo'));
+			f.firstChild.appendChild(document.createTextNode('Test'));
+			f.appendChild(foo.cloneNode(true));
+			chai.assert.equal(
+				evaluateXPathToString('string($f)', undefined, undefined, { f }),
+				'TestTest',
+			);
+			chai.assert.equal(evaluateXPathToString('string(.)', f, undefined), 'TestTest');
+		});
+
 		it('If $arg is the empty sequence, the function returns the zero-length string.', () =>
 			chai.assert.equal(evaluateXPathToString('string(())', documentNode), ''));