/**
* Formatting utilities for RIS MCP Server responses.
*
* This module provides functions to format RIS API responses for optimal
* LLM consumption, including proper Austrian legal citations, search results
* formatting, and document content preparation.
*/
import type { Document, SearchResult } from './types.js';
/** Hard limit for a single text response, in characters. */
export declare const CHARACTER_LIMIT = 25000;
/**
* Convert API date format to readable German format.
*/
export declare function formatDate(dateStr: string | null | undefined): string;
/**
* Convert HTML to clean readable text using cheerio.
*/
export declare function htmlToText(htmlContent: string): string;
/**
* Reduce a full RIS *website* page to the document it displays.
*
* `ris_dokument` mostly fetches bare document HTML, but two of its routes serve
* a rendered page instead — `GeltendeFassung.wxe` (a whole consolidated law) and
* `Dokument.wxe` (a single norm). Those carry the site around the text: skip
* links ("Seitenbereiche:"), the header menu, a version navigation, the footer
* ("Zum Seitenanfang", the Bundeskanzleramt copyright line). Handed to
* {@link htmlToText} whole, the reader gets the navigation first and the law
* somewhere below it, and {@link extractOutline} offers the chrome headings as
* jump targets (issue #94).
*
* The site is dropped by *keeping* `.documentContent` rather than by removing
* the chrome, because the chrome is not addressable: the version navigation on
* `Dokument.wxe` is an attribute-less `
` that no selector can name. The one
* exception is `.Warning` — chrome by position, content by meaning, since it
* says whether the Fassung below is still in force. It is kept from its place in
* the document, not appended, so it stays above the text it qualifies.
*
* `.onlyScreenreader` survives inside the kept fragments: unlike the `.sr-only`
* pairs htmlToText drops as spoken duplicates, it is the only carrier of the
* `§ n` section markers of a consolidated law.
*
* A page without `.documentContent` — every bare document route — is returned
* as the identical string, so the far more common path is not re-serialised.
*/
export declare function scopeRisContent(html: string): {
html: string;
pageTitle: string | null;
};
/**
* Document data for citation formatting.
*/
interface CitationData {
applikation?: string;
titel?: string;
kurztitel?: string | null;
dokumentnummer?: string;
citation?: {
kurztitel?: string | null;
langtitel?: string | null;
kundmachungsorgan?: string | null;
paragraph?: string | null;
inkrafttreten?: string | null;
};
}
/**
* Format a proper Austrian legal citation.
*/
export declare function formatCitation(doc: Document | CitationData): string;
/**
* Format search results for display.
*/
export declare function formatSearchResults(results: SearchResult | Record, format?: 'markdown' | 'json'): string;
/**
* Metadata for document formatting.
*/
export interface DocumentMetadata {
dokumentnummer?: string;
applikation?: string;
titel?: string;
kurztitel?: string | null;
citation?: {
kurztitel?: string | null;
langtitel?: string | null;
kundmachungsorgan?: string | null;
paragraph?: string | null;
eli?: string | null;
inkrafttreten?: string | null;
ausserkrafttreten?: string | null;
};
dokument_url?: string | null;
gesamte_rechtsvorschrift_url?: string | null;
}
/**
* Format a full document for LLM context.
*/
export declare function formatDocument(content: string, metadata: DocumentMetadata, format?: 'markdown' | 'json'): string;
/** One jump target in a document, as offered by the viewer's outline. */
export interface OutlineEntry {
/**
* Heading level 1–6, taken from the RIS source markup. Level 1 is the RIS
* record layer (field labels plus, in gazettes, the masthead); levels 2 and
* below are structure inside the document text.
*/
level: number;
/** Heading text, whitespace-normalised exactly as the document text is. */
label: string;
/** Character offset of the heading's line in the document text. */
offset: number;
/**
* Characters up to the next heading, or to the end of the text for the last
* entry. The only reliable way to tell a one-line metadata field from a real
* section: RIS gives both the same element and the same class.
*/
span: number;
}
/**
* Extract the jump targets of a document.
*
* `html` is the RIS source, `text` the markdown rendering it produced. The
* headings are read from the markup — the rendered text carries no structure
* beyond the three headings formatDocument() writes itself — and then located in
* `text` by their own line.
*
* A document without headings yields `[]`. That is a valid outline, not an
* error: the viewer stays navigable through plain offset paging.
*/
export declare function extractOutline(html: string, text: string): OutlineEntry[];
/** One slice of a document text, addressed by character offset. */
export interface ResponseChunk {
/** The slice starting at the requested offset, at most `limit` characters. */
text: string;
/** Length of the complete text this chunk was cut from. */
total_length: number;
/** Offset for the following chunk, or null when this chunk ends the text. */
next_offset: number | null;
}
/**
* Cut one chunk out of `text`, starting at `offset`.
*
* Chunk boundaries follow the same paragraph-then-sentence rule as
* {@link truncateResponse}; unlike that function nothing is appended and nothing
* is dropped — consecutive chunks concatenate back to `text`.
*
* Offsets are character offsets into the complete text, i.e. the unit of
* `String.length` and `String.slice`. They stay valid as long as the same text
* does, which is why every chunk carries `total_length`: a caller whose text was
* re-fetched in between sees the length change and restarts at offset 0.
*/
export declare function chunkResponse(text: string, offset?: number, limit?: number): ResponseChunk;
/**
* Truncate response if too long.
*/
export declare function truncateResponse(text: string, limit?: number): string;
export {};
//# sourceMappingURL=formatting.d.ts.map