The module can download Lovdata's open data packages, but they arrive as .tar.bz2 archives of XML that a user then has to unpack and parse themselves. The information inside is rich — every act and regulation carries a full metadata record and a structured body — and none of it is reachable as PowerShell objects today.
Request
Desired capability
Two things, which together make the open corpus usable from PowerShell without an account:
- A bundled index. The module ships with the complete metadata record for every document Lovdata publishes openly, refreshed daily, so a user can search, filter, and cross-reference the whole body of Norwegian law instantly and offline — no download, no unpacking, no network.
- Full-fidelity documents on demand. Pointing a command at a downloaded archive returns the complete document as an object hierarchy: metadata, chapter and paragraph structure, clauses, lists, footnotes, and change notes, with nothing dropped.
The bundled index follows the pattern already used by PSModule/GoogleFonts and PSModule/NerdFonts: a scheduled job refreshes the data and opens a pull request when it changes, so a module release always carries a current dataset.
Acceptance criteria
- Every document Lovdata publishes openly is queryable from the bundled index with no network access
- The index carries every metadata field Lovdata publishes, not a chosen subset
- A document from an archive parses into an object hierarchy that preserves its chapter and paragraph structure, and every field round-trips without loss
- Metadata that is not a clean value — a date field carrying two dates or a prose qualifier — is exposed without being silently truncated or dropped
- A daily job refreshes the bundled index and opens a pull request only when the data actually changed
- The index is regenerated deterministically, so an unchanged corpus produces no diff
- Everything works with no API key
Measurements from the live corpus
Taken from gjeldende-lover.tar.bz2 and gjeldende-sentrale-forskrifter.tar.bz2 on 2026-08-02:
|
Documents |
Unpacked XML |
nl — acts |
759 |
50.9 MB |
sf — central regulations |
3 426 |
198.8 MB |
| Total |
4 185 |
249.7 MB |
- Metadata-only index for all 4 185 documents: 10.0 MB of JSON, 2.2 MB packaged. Build time about 30 s.
- Full fidelity including document text: roughly 290 MB of JSON, regenerated daily.
References
Technical decisions
What is bundled, and what is not. The index carries metadata only; document text is parsed on demand from a downloaded archive. Bundling the text would mean a ~290 MB dataset rewritten daily, which makes the repository unusable within weeks and is roughly 200 times the size of the GoogleFonts dataset. It is also the more dangerous of the two to bundle: a stale font list is harmless, while law text that looks authoritative but quotes a repealed provision is not. Metadata goes stale far more gracefully, and the index records when it was built so a caller can judge it.
Object model derived from the corpus, not from a sample. All 4 185 documents were scanned before designing the model. The findings that shape it:
dateInForce is a clean ISO date in only 512 of 732 documents that have it. The rest carry several dates (1965-07-01, 1967-04-23) or a prose qualifier (1966-05-06 med virkning fra 1963-01-01).
lastupdated is never a bare date — all 577 occurrences carry a parenthetical note, for example 2021-07-01 (faglige noter fjernet, struktur).
legalArea holds more than one area in 503 of 759 acts, and each area is a hierarchical path with its own identifiers: legal-areas/01 → legal-areas/01.02.
ministry is a list, though only one document uses more than one entry.
eeaReferences, changesToDocuments, lastChangedBy, and miscInformation embed links to other documents by refID.
Every date-bearing field therefore keeps its raw text alongside any parsed value, and parsed dates are a collection rather than a single value. No field is reduced to a [datetime] that would discard what Lovdata actually published.
Keyed on class, not on the visible label. Metadata is a dt/dd list whose visible labels vary by target form (I kraft frå versus I kraft fra); the class attribute is stable. Confirmed against the corpus: 17 distinct metadata keys, of which title, legacyID, refid, dokid, ministry and table-of-contents appear on every document, down to appliesTo on two.
Parsing approach. Every one of the 4 185 files loads cleanly into [System.Xml.XmlDocument] once the leading <!DOCTYPE html> is stripped — verified across the whole corpus, 1 159 files in 1.8 s. No HTML-tolerant parser or third-party dependency is needed. The public web pages do not parse this way, which is a concrete reason to prefer the packages.
One parser, two callers. The updater script uses the module's own parsing functions rather than carrying a second implementation, so the code that builds the shipped index is the same code that is unit tested.
Index format. One JSON file per legal source under src/, rather than a single combined file, so a daily diff touches only the source that changed. Written with a stable key order and sorted documents so an unchanged corpus produces a byte-identical file and therefore no pull request.
Update mechanism. A scheduled workflow mirroring GoogleFonts: daily cron plus workflow_dispatch, authenticated as a GitHub App, committing to an auto-update-<timestamp> branch and opening a pull request, superseding any earlier open auto-update pull request. No API key is needed, since the packages are open.
.gitattributes. The generated JSON is marked text eol=lf so a core.autocrlf=true clone does not report a spurious diff. Noted in #17 as a real trap hit by the S62 session.
Open: whether lovtidend-avd1-* should also be indexed. Those are the chronological gazette volumes rather than the consolidated current law, so they duplicate content already covered and would add substantial size. Excluded for now; revisit if a use case appears.
Implementation plan
Object model
Parsing
Commands
Bundled data and updater
Tests
The module can download Lovdata's open data packages, but they arrive as
.tar.bz2archives of XML that a user then has to unpack and parse themselves. The information inside is rich — every act and regulation carries a full metadata record and a structured body — and none of it is reachable as PowerShell objects today.Request
Desired capability
Two things, which together make the open corpus usable from PowerShell without an account:
The bundled index follows the pattern already used by
PSModule/GoogleFontsandPSModule/NerdFonts: a scheduled job refreshes the data and opens a pull request when it changes, so a module release always carries a current dataset.Acceptance criteria
Measurements from the live corpus
Taken from
gjeldende-lover.tar.bz2andgjeldende-sentrale-forskrifter.tar.bz2on 2026-08-02:nl— actssf— central regulationsReferences
Technical decisions
What is bundled, and what is not. The index carries metadata only; document text is parsed on demand from a downloaded archive. Bundling the text would mean a ~290 MB dataset rewritten daily, which makes the repository unusable within weeks and is roughly 200 times the size of the
GoogleFontsdataset. It is also the more dangerous of the two to bundle: a stale font list is harmless, while law text that looks authoritative but quotes a repealed provision is not. Metadata goes stale far more gracefully, and the index records when it was built so a caller can judge it.Object model derived from the corpus, not from a sample. All 4 185 documents were scanned before designing the model. The findings that shape it:
dateInForceis a clean ISO date in only 512 of 732 documents that have it. The rest carry several dates (1965-07-01, 1967-04-23) or a prose qualifier (1966-05-06 med virkning fra 1963-01-01).lastupdatedis never a bare date — all 577 occurrences carry a parenthetical note, for example2021-07-01 (faglige noter fjernet, struktur).legalAreaholds more than one area in 503 of 759 acts, and each area is a hierarchical path with its own identifiers:legal-areas/01→legal-areas/01.02.ministryis a list, though only one document uses more than one entry.eeaReferences,changesToDocuments,lastChangedBy, andmiscInformationembed links to other documents byrefID.Every date-bearing field therefore keeps its raw text alongside any parsed value, and parsed dates are a collection rather than a single value. No field is reduced to a
[datetime]that would discard what Lovdata actually published.Keyed on
class, not on the visible label. Metadata is adt/ddlist whose visible labels vary by target form (I kraft fråversusI kraft fra); theclassattribute is stable. Confirmed against the corpus: 17 distinct metadata keys, of whichtitle,legacyID,refid,dokid,ministryandtable-of-contentsappear on every document, down toappliesToon two.Parsing approach. Every one of the 4 185 files loads cleanly into
[System.Xml.XmlDocument]once the leading<!DOCTYPE html>is stripped — verified across the whole corpus, 1 159 files in 1.8 s. No HTML-tolerant parser or third-party dependency is needed. The public web pages do not parse this way, which is a concrete reason to prefer the packages.One parser, two callers. The updater script uses the module's own parsing functions rather than carrying a second implementation, so the code that builds the shipped index is the same code that is unit tested.
Index format. One JSON file per legal source under
src/, rather than a single combined file, so a daily diff touches only the source that changed. Written with a stable key order and sorted documents so an unchanged corpus produces a byte-identical file and therefore no pull request.Update mechanism. A scheduled workflow mirroring
GoogleFonts: daily cron plusworkflow_dispatch, authenticated as a GitHub App, committing to anauto-update-<timestamp>branch and opening a pull request, superseding any earlier open auto-update pull request. No API key is needed, since the packages are open..gitattributes. The generated JSON is markedtext eol=lfso acore.autocrlf=trueclone does not report a spurious diff. Noted in #17 as a real trap hit by the S62 session.Open: whether
lovtidend-avd1-*should also be indexed. Those are the chronological gazette volumes rather than the consolidated current law, so they duplicate content already covered and would add substantial size. Excluded for now; revisit if a use case appears.Implementation plan
Object model
LovdataDocumentundersrc/classes/public/carrying every metadata fieldLovdataLegalAreamodelling the hierarchical area path with identifiersLovdataDateInfoholding the raw text plus any parsed dates and noteLovdataReferencefor the linked-document referencesLovdataTocEntryfor the table of contentsParsing
dt/ddlist byclassCommands
Get-LovdataDocument— read from the bundled index, or from an extracted archive for full fidelityFind-LovdataDocument— search the index by title, short title, identifier, ministry, or legal areaGet-LovdataLegalArea— the legal-area taxonomy, derived from the indexExpand-LovdataPublicDataset— unpack a downloaded packageBundled data and updater
scripts/Update-LovdataData.ps1reusing the module's parser.github/workflows/Update-LovdataData.ymlon a daily scheduletext eol=lfattribute for the generated dataTests