Skip to content

Render Lovdata documents as Markdown #19

Description

Norwegian legal work routinely needs a law as readable, diffable text rather than as a web page or an object graph. Today that means opening Lovdata, copying the text, and formatting it by hand — once per law, and again whenever the law changes.

Request

Desired capability

A command that renders a Lovdata document as Markdown: a metadata header table, the chapter and paragraph structure preserved as headings, each ledd as its own paragraph, and a source attribution. Plus a form that writes the result to a file, so a whole set of laws can be regenerated in one pass.

The prior art, and why this is worth doing

Twelve Norwegian acts have already been rendered this way by hand for a real legal matter — veglova, sameigelova, servituttlova, hevdslova, jordskiftelova, burettslagslova, eierseksjonslova, friluftsloven, markaloven and others. That is this module's first concrete consumer, and it comes from actual casework rather than a hypothetical.

Those hand-made files have drifted from each other, which is the argument for the feature rather than an inconvenience beside it:

  • The short title is parenthesised in one file and bracketed in another.
  • One file opens with an empty | | | header row; the others start at | Dato | ... |, so the first data row is rendered as the table header.
  • The source URL appears as both https://lovdata.no/lov/<id> and https://lovdata.no/dokument/NL/lov/<id>.
  • One file carries a verification note and omits the --- divider the others have.
  • Rettet appears only where it applies, and one law has an identifier with no sequence number.

Six near-identical documents produced by hand have six slightly different shapes. Generating them removes both the drift and the manual fetch-and-convert step behind it. Defining one canonical shape and normalising to it is part of the deliverable.

Acceptance criteria

  • Rendering a law produces the canonical shape: metadata header table, ## per chapter, ### § N per paragraph, one paragraph per ledd, source attribution
  • Paragraph identifiers with letter suffixes render correctly — § 1 a exists and is common
  • Repealed provisions render as a repeal marker rather than being dropped or left empty
  • Identifiers with no sequence number render correctly
  • Metadata rows are omitted when the source document does not carry them, rather than emitted empty
  • Regenerating an already hand-made law produces the canonical shape with metadata matching what Lovdata publishes
  • The source link is correct and derived, not guessed
  • NLOD 2.0 attribution appears in the generated output
  • Works with no API key

Technical decisions

This does not need the authenticated API. The brief for this issue assumed a dependency on #6 and #7, and on the credential work in #15. It has none. The open gjeldende-lover.tar.bz2 package carries the full text and complete metadata of every current act, key-free — verified by locating all of the hand-made laws in it:

Law Identifier Archive file
friluftsloven LOV-1957-06-28-16 nl-19570628-016.xml
veglova LOV-1963-06-21-23 nl-19630621-023.xml
sameigelova LOV-1965-06-18-6 nl-19650618-006.xml
hevdslova LOV-1966-12-09-1 nl-19661209-001.xml
servituttlova LOV-1968-11-29 nl-19681129-000.xml
burettslagslova LOV-2003-06-06-39 nl-20030606-039.xml
markaloven LOV-2009-06-05-35 nl-20090605-035.xml
jordskiftelova LOV-2013-06-21-100 nl-20130621-100.xml

So this builds on the object model in #18 and ships in the same key-free release. servituttlova is the no-sequence-number case, and its archive file uses 000 in that position.

Metadata mapping, verified against the archive rather than assumed. Every row in the target header maps to a metadata key already inventoried in #18. Checked against veglova:

Header row Metadata key Value in the archive
Dato legacyID LOV-1963-06-21-23
Departement ministry Samferdselsdepartementet
Sist endret lastChangedBy lov/2024-12-06-74 fra 2025-01-01
Ikrafttredelse dateInForce 1964-01-01
Rettet lastupdated 2022-09-27 (rettet trykkfeil i § 54 annet ledd)
Korttittel titleShort Veglova – vegl

Two of these confirm design decisions taken in #18. Rettet is lastupdated, which never appears as a bare date anywhere in the corpus — the parenthetical note is load-bearing, which is why that field keeps its raw text. And veglova labels its own fields in Nynorsk (I kraft frå, Sist endra ved), so reading the visible label rather than the class attribute would fail on exactly this law.

The source URL is derived, not chosen. dokid is NL/lov/1963-06-21-23, so the canonical link is https://lovdata.no/dokument/<dokid>. That settles the drift between the two URL forms in the hand-made files: the dokument/NL/... form is correct, and it is derivable rather than assembled by hand.

Repeal markers are real and common. 855 articles in the first 300 acts alone carry a repeal marker, as (Oppheva or (Opphevet in the paragraph heading. They must render as an explicit marker; dropping them would silently renumber a law.

Command surface. ConvertTo-LovdataMarkdown takes a document object and emits a string; Export-LovdataMarkdown writes it to a file. This follows the data-module verb vocabulary in Module typesConvertTo-<Format> for the object-to-format direction, Export- for writing to a store.

The pivot is the neutral object, not the raw response. ConvertTo-LovdataMarkdown accepts the LovdataDocument object model from #18 rather than raw XML. That keeps one parser, means the renderer is testable without any archive, and means any future source of documents — including the authenticated endpoints in #6 — renders through the same path for free.

Test fixtures are generated, never copied. The hand-made files are a shape reference only. Fixtures are produced from the open Lovdata packages so the module's test data has clean public provenance, and the hand-made files are not treated as byte-exact goldens, since they disagree with each other. The canonical shape defined in this issue is the expected output.

Open: whether the metadata header should be a Markdown table or a YAML front-matter block. A table matches the existing files and reads well on GitHub; front matter is machine-readable and conventional for generated documents. A switch supporting both is a reasonable resolution. Decide before implementation.


Implementation plan

Canonical shape

  • Define the canonical Markdown shape in src/functions/public/<Group>/<Group>.md, resolving each drift listed above
  • Resolve the open question on table versus front matter

Rendering

  • Add ConvertTo-LovdataMarkdown accepting a LovdataDocument
  • Render the metadata header, omitting rows the document does not carry
  • Render chapters as ## and paragraphs as ### § N, preserving letter suffixes
  • Render each ledd as its own paragraph, and lists as Markdown lists
  • Render repeal markers explicitly
  • Render footnotes
  • Derive the source link from dokid and include the NLOD 2.0 attribution
  • Add Export-LovdataMarkdown writing to a file, with -Force and SupportsShouldProcess

Tests

  • Generate fixtures from the open packages; do not copy any existing file
  • Cover the letter-suffix, no-sequence-number, repealed-provision, and missing-metadata-row cases
  • Cover a Nynorsk-labelled document, to lock in that rendering keys on class
  • Assert the generated output for one law against the canonical shape

Documentation

  • Add an example that regenerates a set of laws from the open packages

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew featureminorNew feature or enhancement, version 0.x.0 increase

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions