# core/normalizers/reference.normalizer.ts

> 44 lines of code and 20 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-normalizers-reference-normalizer-ts
Source text: https://banes-lab.com/source/docs/core/normalizers/reference.normalizer.ts.txt

Listed in [core/normalizers](https://banes-lab.com/api/source/docs/core/normalizers.md), after [core/normalizers/diagram.normalizer.ts](https://banes-lab.com/source/docs/core/normalizers/diagram.normalizer.ts.md).

## Definitions

- `unquote` (lexical_declaration, line 11, exported)
- `cleanTarget` (lexical_declaration, line 22, exported)
- `stripLineSuffix` (lexical_declaration, line 37)
- `withoutFragment` (lexical_declaration, line 17, exported)
- `isLineRef` (lexical_declaration, line 26)
- `normalizeTarget` (lexical_declaration, line 48, exported)
- `MIN_QUOTED_LEN` (lexical_declaration, line 3)
- `QUOTES` (lexical_declaration, line 4)
- `FRAGMENT_MARK` (lexical_declaration, line 5)
- `TRAILING_SLASH` (lexical_declaration, line 6)
- `LINE_SEPARATOR` (lexical_declaration, line 7)
- `RANGE_MARK` (lexical_declaration, line 8)
- `MAX_LINE_SUFFIX_PASSES` (lexical_declaration, line 9)
- `quoted` (lexical_declaration, line 12, exported)
- `hash` (lexical_declaration, line 18, exported)
- `sawDigit` (lexical_declaration, line 27)
- `out` (lexical_declaration, line 38)
- `pass` (lexical_declaration, line 39)
- `colon` (lexical_declaration, line 40)
- `target` (lexical_declaration, line 49, exported)

## Contained in

- [core/normalizers](https://banes-lab.com/anatomy/docs/folder-docs-core-normalizers.md)

## Used by

- [core/validators/manifest.prose.validator.ts](https://banes-lab.com/source/docs/core/validators/manifest.prose.validator.ts.md)

## Linked from

- [core/normalizers](https://banes-lab.com/anatomy/docs/folder-docs-core-normalizers.md)
- [core/validators](https://banes-lab.com/anatomy/docs/folder-docs-core-validators.md)

## Source

```typescript
import { isDigit } from "@govlab/constants";

const MIN_QUOTED_LEN = 2;
const QUOTES: readonly string[] = ['"', "'"];
const FRAGMENT_MARK = "#";
const TRAILING_SLASH = "/";
const LINE_SEPARATOR = ":";
const RANGE_MARK = "-";
const MAX_LINE_SUFFIX_PASSES = 2;

export const unquote = function unquote(target: string): string {
    const quoted =
        target.length >= MIN_QUOTED_LEN && QUOTES.some((quote) => target.startsWith(quote) && target.endsWith(quote));
    return quoted ? target.slice(1, -1) : target;
};

export const withoutFragment = function withoutFragment(target: string): string {
    const hash = target.indexOf(FRAGMENT_MARK);
    return hash === -1 ? target : target.slice(0, hash);
};

export const cleanTarget = function cleanTarget(rawPath: string): string {
    return withoutFragment(unquote(rawPath));
};

const isLineRef = function isLineRef(text: string): boolean {
    let sawDigit = false;
    for (const char of text) {
        if (!isDigit(char) && char !== RANGE_MARK) {
            return false;
        }
        sawDigit ||= isDigit(char);
    }
    return sawDigit;
};

const stripLineSuffix = function stripLineSuffix(target: string): string {
    let out = target;
    for (let pass = 0; pass < MAX_LINE_SUFFIX_PASSES; pass += 1) {
        const colon = out.lastIndexOf(LINE_SEPARATOR);
        if (colon > 0 && isLineRef(out.slice(colon + 1))) {
            out = out.slice(0, colon);
        }
    }
    return out;
};

export const normalizeTarget = function normalizeTarget(refPath: string): string {
    const target = withoutFragment(refPath);
    return stripLineSuffix(target.endsWith(TRAILING_SLASH) ? target.slice(0, -1) : target);
};
```
