Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

1
node_modules/parse-latin/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export { ParseLatin } from "./lib/index.js";

1
node_modules/parse-latin/index.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export {ParseLatin} from './lib/index.js'

11
node_modules/parse-latin/lib/expressions.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
export const affixSymbol: RegExp;
export const newLine: RegExp;
export const terminalMarker: RegExp;
export const wordSymbolInner: RegExp;
export const numerical: RegExp;
export const digitStart: RegExp;
export const lowerInitial: RegExp;
export const surrogates: RegExp;
export const punctuation: RegExp;
export const word: RegExp;
export const whiteSpace: RegExp;

19
node_modules/parse-latin/lib/expressions.js generated vendored Normal file

File diff suppressed because one or more lines are too long

82
node_modules/parse-latin/lib/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,82 @@
/**
* Create a new parser.
*/
export class ParseLatin {
/**
* Create a new parser.
*
* This additionally supports `retext`-like call: where an instance is
* created for each file, and the file is given on construction.
*
* @param {string | null | undefined} [doc]
* Value to parse (optional).
* @param {VFile | null | undefined} [file]
* Corresponding file (optional).
*/
constructor(doc?: string | null | undefined, file?: VFile | null | undefined);
/** @type {string | undefined} */
doc: string | undefined;
/** @type {Array<Plugin<Root>>} */
tokenizeRootPlugins: Array<Plugin<Root>>;
/** @type {Array<Plugin<Paragraph>>} */
tokenizeParagraphPlugins: Array<Plugin<Paragraph>>;
/** @type {Array<Plugin<Sentence>>} */
tokenizeSentencePlugins: Array<Plugin<Sentence>>;
/**
* Turn natural language into a syntax tree.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Root}
* Tree.
*/
parse(value?: string | null | undefined): Root;
/**
* Parse as a root.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Root}
* Built tree.
*/
tokenizeRoot(value?: string | null | undefined): Root;
/**
* Parse as a paragraph.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Paragraph}
* Built tree.
*/
tokenizeParagraph(value?: string | null | undefined): Paragraph;
/**
* Parse as a sentence.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Sentence}
* Built tree.
*/
tokenizeSentence(value?: string | null | undefined): Sentence;
/**
* Transform a `value` into a list of nlcsts.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Array<SentenceContent>}
* Built sentence content.
*/
tokenize(value?: string | null | undefined): Array<SentenceContent>;
}
export type Nodes = import('nlcst').Nodes;
export type Parents = import('nlcst').Parents;
export type Paragraph = import('nlcst').Paragraph;
export type Root = import('nlcst').Root;
export type RootContent = import('nlcst').RootContent;
export type Sentence = import('nlcst').Sentence;
export type SentenceContent = import('nlcst').SentenceContent;
export type VFile = import('vfile').VFile;
/**
* Transform a node.
*/
export type Plugin<Node extends import("nlcst").Nodes> = (node: Node) => undefined | void;

352
node_modules/parse-latin/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,352 @@
/**
* @typedef {import('nlcst').Nodes} Nodes
* @typedef {import('nlcst').Parents} Parents
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('nlcst').Root} Root
* @typedef {import('nlcst').RootContent} RootContent
* @typedef {import('nlcst').Sentence} Sentence
* @typedef {import('nlcst').SentenceContent} SentenceContent
* @typedef {import('vfile').VFile} VFile
*/
/**
* @template {Nodes} Node
* Node type.
* @callback Plugin
* Transform a node.
* @param {Node} node
* The node.
* @returns {undefined | void}
* Nothing.
*/
import {toString} from 'nlcst-to-string'
import {mergeAffixExceptions} from './plugin/merge-affix-exceptions.js'
import {mergeAffixSymbol} from './plugin/merge-affix-symbol.js'
import {breakImplicitSentences} from './plugin/break-implicit-sentences.js'
import {makeFinalWhiteSpaceSiblings} from './plugin/make-final-white-space-siblings.js'
import {makeInitialWhiteSpaceSiblings} from './plugin/make-initial-white-space-siblings.js'
import {mergeFinalWordSymbol} from './plugin/merge-final-word-symbol.js'
import {mergeInitialDigitSentences} from './plugin/merge-initial-digit-sentences.js'
import {mergeInitialLowerCaseLetterSentences} from './plugin/merge-initial-lower-case-letter-sentences.js'
import {mergeInitialWordSymbol} from './plugin/merge-initial-word-symbol.js'
import {mergeInitialisms} from './plugin/merge-initialisms.js'
import {mergeInnerWordSymbol} from './plugin/merge-inner-word-symbol.js'
import {mergeInnerWordSlash} from './plugin/merge-inner-word-slash.js'
import {mergeNonWordSentences} from './plugin/merge-non-word-sentences.js'
import {mergePrefixExceptions} from './plugin/merge-prefix-exceptions.js'
import {mergeRemainingFullStops} from './plugin/merge-remaining-full-stops.js'
import {removeEmptyNodes} from './plugin/remove-empty-nodes.js'
import {patchPosition} from './plugin/patch-position.js'
import {
newLine,
punctuation,
surrogates,
terminalMarker,
whiteSpace,
word
} from './expressions.js'
// PARSE LATIN
/**
* Create a new parser.
*/
export class ParseLatin {
/**
* Create a new parser.
*
* This additionally supports `retext`-like call: where an instance is
* created for each file, and the file is given on construction.
*
* @param {string | null | undefined} [doc]
* Value to parse (optional).
* @param {VFile | null | undefined} [file]
* Corresponding file (optional).
*/
constructor(doc, file) {
const value = file || doc
/** @type {string | undefined} */
this.doc = value ? String(value) : undefined
/** @type {Array<Plugin<Root>>} */
this.tokenizeRootPlugins = [...this.tokenizeRootPlugins]
/** @type {Array<Plugin<Paragraph>>} */
this.tokenizeParagraphPlugins = [...this.tokenizeParagraphPlugins]
/** @type {Array<Plugin<Sentence>>} */
this.tokenizeSentencePlugins = [...this.tokenizeSentencePlugins]
}
/**
* Turn natural language into a syntax tree.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Root}
* Tree.
*/
parse(value) {
return this.tokenizeRoot(value || this.doc)
}
/**
* Parse as a root.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Root}
* Built tree.
*/
tokenizeRoot(value) {
const paragraph = this.tokenizeParagraph(value)
/** @type {Root} */
const result = {
type: 'RootNode',
children: splitNode(paragraph, 'WhiteSpaceNode', newLine)
}
let index = -1
while (this.tokenizeRootPlugins[++index]) {
this.tokenizeRootPlugins[index](result)
}
return result
}
/**
* Parse as a paragraph.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Paragraph}
* Built tree.
*/
tokenizeParagraph(value) {
const sentence = this.tokenizeSentence(value)
/** @type {Paragraph} */
const result = {
type: 'ParagraphNode',
children: splitNode(sentence, 'PunctuationNode', terminalMarker)
}
let index = -1
while (this.tokenizeParagraphPlugins[++index]) {
this.tokenizeParagraphPlugins[index](result)
}
return result
}
/**
* Parse as a sentence.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Sentence}
* Built tree.
*/
tokenizeSentence(value) {
const children = this.tokenize(value)
/** @type {Sentence} */
const result = {type: 'SentenceNode', children}
let index = -1
while (this.tokenizeSentencePlugins[++index]) {
this.tokenizeSentencePlugins[index](result)
}
return result
}
/**
* Transform a `value` into a list of nlcsts.
*
* @param {string | null | undefined} [value]
* Value to parse (optional).
* @returns {Array<SentenceContent>}
* Built sentence content.
*/
tokenize(value) {
/** @type {Array<SentenceContent>} */
const children = []
if (!value) {
return children
}
const currentPoint = {line: 1, column: 1, offset: 0}
let from = 0
let index = 0
let start = {...currentPoint}
/** @type {SentenceContent['type'] | undefined} */
let previousType
/** @type {string | undefined} */
let previous
while (index < value.length) {
const current = value.charAt(index)
const currentType = whiteSpace.test(current)
? 'WhiteSpaceNode'
: punctuation.test(current)
? 'PunctuationNode'
: word.test(current)
? 'WordNode'
: 'SymbolNode'
if (
from < index &&
previousType &&
currentType &&
!(
previousType === currentType &&
// Words or white space continue.
(previousType === 'WordNode' ||
previousType === 'WhiteSpaceNode' ||
// Same character of punctuation or symbol also continues.
current === previous ||
// Surrogates of punctuation or symbol also continue.
surrogates.test(current))
)
) {
// Flush the previous queue.
children.push(createNode(previousType, value.slice(from, index)))
from = index
start = {...currentPoint}
}
if (current === '\r' || (current === '\n' && previous !== '\r')) {
currentPoint.line++
currentPoint.column = 1
} else if (current !== '\n') {
currentPoint.column++
}
currentPoint.offset++
previousType = currentType
previous = current
index++
}
if (previousType && from < index) {
children.push(createNode(previousType, value.slice(from, index)))
}
return children
/**
* @param {SentenceContent['type']} type
* Node type to build.
* @param {string} value
* Value.
* @returns {SentenceContent}
* Node.
*/
function createNode(type, value) {
return type === 'WordNode'
? {
type: 'WordNode',
children: [
{
type: 'TextNode',
value,
position: {start, end: {...currentPoint}}
}
],
position: {start, end: {...currentPoint}}
}
: {type, value, position: {start, end: {...currentPoint}}}
}
}
}
/**
* List of transforms handling a sentence.
*/
ParseLatin.prototype.tokenizeSentencePlugins = [
mergeInitialWordSymbol,
mergeFinalWordSymbol,
mergeInnerWordSymbol,
mergeInnerWordSlash,
mergeInitialisms,
patchPosition
]
/**
* List of transforms handling a paragraph.
*/
ParseLatin.prototype.tokenizeParagraphPlugins = [
mergeNonWordSentences,
mergeAffixSymbol,
mergeInitialLowerCaseLetterSentences,
mergeInitialDigitSentences,
mergePrefixExceptions,
mergeAffixExceptions,
mergeRemainingFullStops,
makeInitialWhiteSpaceSiblings,
makeFinalWhiteSpaceSiblings,
breakImplicitSentences,
removeEmptyNodes,
patchPosition
]
/**
* List of transforms handling a root.
*/
ParseLatin.prototype.tokenizeRootPlugins = [
makeInitialWhiteSpaceSiblings,
makeFinalWhiteSpaceSiblings,
removeEmptyNodes,
patchPosition
]
/**
* A function that splits one node into several nodes.
*
* @template {Parents} Node
* Node type.
* @param {Node} node
* Node to split.
* @param {RegExp} expression
* Split on this regex.
* @param {Node['children'][number]['type']} childType
* Split this node type.
* @returns {Array<Node>}
* The given node, split into several nodes.
*/
function splitNode(node, childType, expression) {
/** @type {Array<Node>} */
const result = []
let index = -1
let start = 0
while (++index < node.children.length) {
const token = node.children[index]
if (
index === node.children.length - 1 ||
(token.type === childType && expression.test(toString(token)))
) {
/** @type {Node} */
// @ts-expect-error: fine
const parent = {
type: node.type,
children: node.children.slice(start, index + 1)
}
const first = node.children[start]
const last = token
if (first.position && last.position) {
parent.position = {
start: first.position.start,
end: last.position.end
}
}
result.push(parent)
start = index + 1
}
}
return result
}

View File

@@ -0,0 +1,3 @@
export const breakImplicitSentences: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;
export type Sentence = import('nlcst').Sentence;

View File

@@ -0,0 +1,59 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('nlcst').Sentence} Sentence
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Break a sentence if a white space with more than one new-line is found.
export const breakImplicitSentences = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
if (child.type !== 'SentenceNode') {
return
}
const children = child.children
// Ignore first and last child.
let position = 0
while (++position < children.length - 1) {
const node = children[position]
if (
node.type !== 'WhiteSpaceNode' ||
toString(node).split(/\r\n|\r|\n/).length < 3
) {
continue
}
child.children = children.slice(0, position)
/** @type {Sentence} */
const insertion = {
type: 'SentenceNode',
children: children.slice(position + 1)
}
const tail = children[position - 1]
const head = children[position + 1]
parent.children.splice(index + 1, 0, node, insertion)
if (child.position && tail.position && head.position) {
const end = child.position.end
child.position.end = tail.position.end
insertion.position = {start: head.position.start, end}
}
return index + 1
}
}
)

View File

@@ -0,0 +1,3 @@
export const makeFinalWhiteSpaceSiblings: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph | import("nlcst").Root>;
export type Paragraph = import('nlcst').Paragraph;
export type Root = import('nlcst').Root;

View File

@@ -0,0 +1,33 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('nlcst').Root} Root
*/
import {modifyChildren} from 'unist-util-modify-children'
// Move white space ending a paragraph up, so they are the siblings of
// paragraphs.
export const makeFinalWhiteSpaceSiblings = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph | Root>}
*/
function (child, index, parent) {
if ('children' in child) {
const tail = child.children[child.children.length - 1]
if (tail && tail.type === 'WhiteSpaceNode') {
child.children.pop() // Remove `tail`.
parent.children.splice(index + 1, 0, tail)
const previous = child.children[child.children.length - 1]
if (previous && previous.position && child.position) {
child.position.end = previous.position.end
}
// Next, iterate over the current node again.
return index
}
}
}
)

View File

@@ -0,0 +1,3 @@
export const makeInitialWhiteSpaceSiblings: import("../../node_modules/unist-util-visit-children/lib/index.js").Visit<import("nlcst").Paragraph | import("nlcst").Root>;
export type Paragraph = import('nlcst').Paragraph;
export type Root = import('nlcst').Root;

View File

@@ -0,0 +1,28 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('nlcst').Root} Root
*/
import {visitChildren} from 'unist-util-visit-children'
// Move white space starting a sentence up, so they are the siblings of
// sentences.
export const makeInitialWhiteSpaceSiblings = visitChildren(
/**
* @type {import('unist-util-visit-children').Visitor<Paragraph | Root>}
*/
function (child, index, parent) {
if ('children' in child && child.children) {
const head = child.children[0]
if (head && head.type === 'WhiteSpaceNode') {
child.children.shift()
parent.children.splice(index, 0, head)
const next = child.children[0]
if (next && next.position && child.position) {
child.position.start = next.position.start
}
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeAffixExceptions: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,54 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Merge a sentence into its previous sentence, when the sentence starts with a
// comma.
export const mergeAffixExceptions = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
const previous = parent.children[index - 1]
if (
previous &&
'children' in previous &&
'children' in child &&
child.children.length > 0
) {
let position = -1
while (child.children[++position]) {
const node = child.children[position]
if (node.type === 'WordNode') {
return
}
if (node.type === 'SymbolNode' || node.type === 'PunctuationNode') {
const value = toString(node)
if (value !== ',' && value !== ';') {
return
}
previous.children.push(...child.children)
// Update position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
parent.children.splice(index, 1)
// Next, iterate over the node *now* at the current position.
return index
}
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeAffixSymbol: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,47 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Closing or final punctuation, or terminal markers that should still be
// included in the previous sentence, even though they follow the sentences
// terminal marker.
import {affixSymbol} from '../expressions.js'
// Move certain punctuation following a terminal marker (thus in the next
// sentence) to the previous sentence.
export const mergeAffixSymbol = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
if ('children' in child && child.children.length > 0 && index > 0) {
const previous = parent.children[index - 1]
const first = child.children[0]
const second = child.children[1]
if (
previous &&
previous.type === 'SentenceNode' &&
(first.type === 'SymbolNode' || first.type === 'PunctuationNode') &&
affixSymbol.test(toString(first))
) {
child.children.shift() // Remove `first`.
previous.children.push(first)
// Update position.
if (first.position && previous.position) {
previous.position.end = first.position.end
}
if (second && second.position && child.position) {
child.position.start = second.position.start
}
// Next, iterate over the previous node again.
return index - 1
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeFinalWordSymbol: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Sentence>;
export type Sentence = import('nlcst').Sentence;

View File

@@ -0,0 +1,45 @@
/**
* @typedef {import('nlcst').Sentence} Sentence
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Merge certain punctuation marks into their preceding words.
export const mergeFinalWordSymbol = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Sentence>}
*/
function (child, index, parent) {
if (
index > 0 &&
(child.type === 'SymbolNode' || child.type === 'PunctuationNode') &&
toString(child) === '-'
) {
const children = parent.children
const previous = children[index - 1]
const next = children[index + 1]
if (
(!next || next.type !== 'WordNode') &&
previous &&
previous.type === 'WordNode'
) {
// Remove `child` from parent.
children.splice(index, 1)
// Add the punctuation mark at the end of the previous node.
previous.children.push(child)
// Update position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
// Next, iterate over the node *now* at the current position (which was
// the next node).
return index
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeInitialDigitSentences: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,39 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
import {digitStart} from '../expressions.js'
// Merge a sentence into its previous sentence, when the sentence starts with a
// lower case letter.
export const mergeInitialDigitSentences = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
const previous = parent.children[index - 1]
if (
previous &&
previous.type === 'SentenceNode' &&
child.type === 'SentenceNode'
) {
const head = child.children[0]
if (head && head.type === 'WordNode' && digitStart.test(toString(head))) {
previous.children.push(...child.children)
parent.children.splice(index, 1)
// Update position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
// Next, iterate over the node *now* at the current position.
return index
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeInitialLowerCaseLetterSentences: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,52 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Initial lowercase letter.
import {lowerInitial} from '../expressions.js'
// Merge a sentence into its previous sentence, when the sentence starts with a
// lower case letter.
export const mergeInitialLowerCaseLetterSentences = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
if (child.type === 'SentenceNode' && index > 0) {
const previous = parent.children[index - 1]
const children = child.children
if (children.length > 0 && previous.type === 'SentenceNode') {
let position = -1
while (children[++position]) {
const node = children[position]
if (node.type === 'WordNode') {
if (!lowerInitial.test(toString(node))) {
return
}
previous.children.push(...children)
parent.children.splice(index, 1)
// Update position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
// Next, iterate over the node *now* at the current position.
return index
}
if (node.type === 'SymbolNode' || node.type === 'PunctuationNode') {
return
}
}
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeInitialWordSymbol: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Sentence>;
export type Sentence = import('nlcst').Sentence;

View File

@@ -0,0 +1,47 @@
/**
* @typedef {import('nlcst').Sentence} Sentence
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Merge certain punctuation marks into their following words.
export const mergeInitialWordSymbol = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Sentence>}
*/
function (child, index, parent) {
if (
(child.type !== 'SymbolNode' && child.type !== 'PunctuationNode') ||
toString(child) !== '&'
) {
return
}
const children = parent.children
const next = children[index + 1]
// If either a previous word, or no following word, exists, exit early.
if (
(index > 0 && children[index - 1].type === 'WordNode') ||
!(next && next.type === 'WordNode')
) {
return
}
// Remove `child` from parent.
children.splice(index, 1)
// Add the punctuation mark at the start of the next node.
next.children.unshift(child)
// Update position.
if (next.position && child.position) {
next.position.start = child.position.start
}
// Next, iterate over the node at the previous position, as it's now adjacent
// to a following word.
return index - 1
}
)

View File

@@ -0,0 +1,2 @@
export const mergeInitialisms: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Sentence>;
export type Sentence = import('nlcst').Sentence;

View File

@@ -0,0 +1,72 @@
/**
* @typedef {import('nlcst').Sentence} Sentence
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
import {numerical} from '../expressions.js'
// Merge initialisms.
export const mergeInitialisms = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Sentence>}
*/
function (child, index, parent) {
if (
index > 0 &&
child.type === 'PunctuationNode' &&
toString(child) === '.'
) {
const previous = parent.children[index - 1]
if (
previous.type === 'WordNode' &&
previous.children &&
previous.children.length !== 1 &&
previous.children.length % 2 !== 0
) {
let position = previous.children.length
let isAllDigits = true
while (previous.children[--position]) {
const otherChild = previous.children[position]
const value = toString(otherChild)
if (position % 2 === 0) {
// Initialisms consist of one character values.
if (value.length > 1) {
return
}
if (!numerical.test(value)) {
isAllDigits = false
}
} else if (value !== '.') {
if (position < previous.children.length - 2) {
break
} else {
return
}
}
}
if (!isAllDigits) {
// Remove `child` from parent.
parent.children.splice(index, 1)
// Add child to the previous children.
previous.children.push(child)
// Update position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
// Next, iterate over the node *now* at the current position.
return index
}
}
}
}
)

View File

@@ -0,0 +1,4 @@
export const mergeInnerWordSlash: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Sentence>;
export type Sentence = import('nlcst').Sentence;
export type SentenceContent = import('nlcst').SentenceContent;
export type WordContent = import('nlcst').WordContent;

View File

@@ -0,0 +1,57 @@
/**
* @typedef {import('nlcst').Sentence} Sentence
* @typedef {import('nlcst').SentenceContent} SentenceContent
* @typedef {import('nlcst').WordContent} WordContent
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Merge words joined by certain punctuation marks.
export const mergeInnerWordSlash = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Sentence>}
*/
function (child, index, parent) {
const siblings = parent.children
const previous = siblings[index - 1]
if (
previous &&
previous.type === 'WordNode' &&
(child.type === 'SymbolNode' || child.type === 'PunctuationNode') &&
toString(child) === '/'
) {
const previousValue = toString(previous)
/** @type {SentenceContent} */
let tail = child
/** @type {Array<WordContent>} */
const queue = [child]
let count = 1
let nextValue = ''
const next = siblings[index + 1]
if (next && next.type === 'WordNode') {
nextValue = toString(next)
tail = next
queue.push(...next.children)
count++
}
if (previousValue.length < 3 && (!nextValue || nextValue.length < 3)) {
// Add all found tokens to `prev`s children.
previous.children.push(...queue)
siblings.splice(index, count)
// Update position.
if (previous.position && tail.position) {
previous.position.end = tail.position.end
}
// Next, iterate over the node *now* at the current position.
return index
}
}
}
)

View File

@@ -0,0 +1,3 @@
export const mergeInnerWordSymbol: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Sentence>;
export type Sentence = import('nlcst').Sentence;
export type WordContent = import('nlcst').WordContent;

View File

@@ -0,0 +1,79 @@
/**
* @typedef {import('nlcst').Sentence} Sentence
* @typedef {import('nlcst').WordContent} WordContent
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Symbols part of surrounding words.
import {wordSymbolInner} from '../expressions.js'
// Merge words joined by certain punctuation marks.
export const mergeInnerWordSymbol = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Sentence>}
*/
function (child, index, parent) {
if (
index > 0 &&
(child.type === 'SymbolNode' || child.type === 'PunctuationNode')
) {
const siblings = parent.children
const previous = siblings[index - 1]
if (previous && previous.type === 'WordNode') {
let position = index - 1
/** @type {Array<WordContent>} */
const tokens = []
/** @type {Array<WordContent>} */
let queue = []
// - If a token which is neither word nor inner word symbol is found,
// the loop is broken
// - If an inner word symbol is found, its queued
// - If a word is found, its queued (and the queue stored and emptied)
while (siblings[++position]) {
const sibling = siblings[position]
if (sibling.type === 'WordNode') {
tokens.push(...queue, ...sibling.children)
queue = []
} else if (
(sibling.type === 'SymbolNode' ||
sibling.type === 'PunctuationNode') &&
wordSymbolInner.test(toString(sibling))
) {
queue.push(sibling)
} else {
break
}
}
if (tokens.length > 0) {
// If there is a queue, remove its length from `position`.
if (queue.length > 0) {
position -= queue.length
}
// Remove every (one or more) inner-word punctuation marks and children
// of words.
siblings.splice(index, position - index)
// Add all found tokens to `prev`s children.
previous.children.push(...tokens)
const last = tokens[tokens.length - 1]
// Update position.
if (previous.position && last.position) {
previous.position.end = last.position.end
}
// Next, iterate over the node *now* at the current position.
return index
}
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeNonWordSentences: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,56 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {modifyChildren} from 'unist-util-modify-children'
// Merge a sentence into the following sentence, when the sentence does not
// contain word tokens.
export const mergeNonWordSentences = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
if ('children' in child) {
let position = -1
while (child.children[++position]) {
if (child.children[position].type === 'WordNode') {
return
}
}
const previous = parent.children[index - 1]
if (previous && 'children' in previous) {
previous.children.push(...child.children)
// Remove the child.
parent.children.splice(index, 1)
// Patch position.
if (previous.position && child.position) {
previous.position.end = child.position.end
}
// Next, iterate over the node *now* at the current position (which was the
// next node).
return index
}
const next = parent.children[index + 1]
if (next && 'children' in next) {
next.children.unshift(...child.children)
// Patch position.
if (next.position && child.position) {
next.position.start = child.position.start
}
// Remove the child.
parent.children.splice(index, 1)
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergePrefixExceptions: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,78 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {modifyChildren} from 'unist-util-modify-children'
// Full stop characters that should not be treated as terminal sentence markers:
// A case-insensitive abbreviation.
const abbreviationPrefix = new RegExp(
'^(' +
'[0-9]{1,3}|' +
'[a-z]|' +
// Common Latin Abbreviations:
// Based on: <https://en.wikipedia.org/wiki/List_of_Latin_abbreviations>.
// Where only the abbreviations written without joining full stops,
// but with a final full stop, were extracted.
//
// circa, capitulus, confer, compare, centum weight, eadem, (et) alii,
// et cetera, floruit, foliis, ibidem, idem, nemine && contradicente,
// opere && citato, (per) cent, (per) procurationem, (pro) tempore,
// sic erat scriptum, (et) sequentia, statim, videlicet. */
'al|ca|cap|cca|cent|cf|cit|con|cp|cwt|ead|etc|ff|' +
'fl|ibid|id|nem|op|pro|seq|sic|stat|tem|viz' +
')$'
)
// Merge a sentence into its next sentence, when the sentence ends with a
// certain word.
export const mergePrefixExceptions = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph>}
*/
function (child, index, parent) {
if ('children' in child && child.children.length > 1) {
const period = child.children[child.children.length - 1]
if (
period &&
(period.type === 'PunctuationNode' || period.type === 'SymbolNode') &&
toString(period) === '.'
) {
const node = child.children[child.children.length - 2]
if (
node &&
node.type === 'WordNode' &&
abbreviationPrefix.test(toString(node).toLowerCase())
) {
// Merge period into abbreviation.
node.children.push(period)
child.children.pop()
// Update position.
if (period.position && node.position) {
node.position.end = period.position.end
}
// Merge sentences.
const next = parent.children[index + 1]
if (next && next.type === 'SentenceNode') {
child.children.push(...next.children)
parent.children.splice(index + 1, 1)
// Update position.
if (next.position && child.position) {
child.position.end = next.position.end
}
// Next, iterate over the current node again.
return index - 1
}
}
}
}
}
)

View File

@@ -0,0 +1,2 @@
export const mergeRemainingFullStops: import("../../node_modules/unist-util-visit-children/lib/index.js").Visit<import("nlcst").Paragraph>;
export type Paragraph = import('nlcst').Paragraph;

View File

@@ -0,0 +1,99 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
*/
import {toString} from 'nlcst-to-string'
import {visitChildren} from 'unist-util-visit-children'
// Full stop characters that should not be treated as terminal sentence markers:
// A case-insensitive abbreviation.
import {terminalMarker} from '../expressions.js'
// Merge non-terminal-marker full stops into the previous word (if available),
// or the next word (if available).
export const mergeRemainingFullStops = visitChildren(
/**
* @type {import('unist-util-visit-children').Visitor<Paragraph>}
*/
// eslint-disable-next-line complexity
function (child, _, _parent) {
if ('children' in child) {
let position = child.children.length
let hasFoundDelimiter = false
while (child.children[--position]) {
const grandchild = child.children[position]
if (
grandchild.type !== 'SymbolNode' &&
grandchild.type !== 'PunctuationNode'
) {
// This is a sentence without terminal marker, so we 'fool' the code to
// make it think we have found one.
if (grandchild.type === 'WordNode') {
hasFoundDelimiter = true
}
continue
}
// Exit when this token is not a terminal marker.
if (!terminalMarker.test(toString(grandchild))) {
continue
}
// Ignore the first terminal marker found (starting at the end), as it
// should not be merged.
if (!hasFoundDelimiter) {
hasFoundDelimiter = true
continue
}
// Only merge a single full stop.
if (toString(grandchild) !== '.') {
continue
}
const previous = child.children[position - 1]
const next = child.children[position + 1]
if (previous && previous.type === 'WordNode') {
const nextNext = child.children[position + 2]
// Continue when the full stop is followed by a space and another full
// stop, such as: `{.} .`
if (
next &&
nextNext &&
next.type === 'WhiteSpaceNode' &&
toString(nextNext) === '.'
) {
continue
}
// Remove `child` from parent.
child.children.splice(position, 1)
// Add the punctuation mark at the end of the previous node.
previous.children.push(grandchild)
// Update position.
if (grandchild.position && previous.position) {
previous.position.end = grandchild.position.end
}
position--
} else if (next && next.type === 'WordNode') {
// Remove `child` from parent.
child.children.splice(position, 1)
// Add the punctuation mark at the start of the next node.
next.children.unshift(grandchild)
if (grandchild.position && next.position) {
next.position.start = grandchild.position.start
}
}
}
}
}
)

View File

@@ -0,0 +1,6 @@
export const patchPosition: import("../../node_modules/unist-util-visit-children/lib/index.js").Visit<import("nlcst").Sentence | import("nlcst").Paragraph | import("nlcst").Root>;
export type Node = import('unist').Node;
export type Paragraph = import('nlcst').Paragraph;
export type Position = import('unist').Position;
export type Root = import('nlcst').Root;
export type Sentence = import('nlcst').Sentence;

49
node_modules/parse-latin/lib/plugin/patch-position.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
/**
* @typedef {import('unist').Node} Node
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('unist').Position} Position
* @typedef {import('nlcst').Root} Root
* @typedef {import('nlcst').Sentence} Sentence
*/
import {visitChildren} from 'unist-util-visit-children'
// Patch the position on a parent node based on its first and last child.
export const patchPosition = visitChildren(
/**
* @type {import('unist-util-visit-children').Visitor<Paragraph | Root | Sentence>}
*/
function (child, index, node) {
const siblings = node.children
if (
child.position &&
index < 1 &&
/* c8 ignore next */
(!node.position || !node.position.start)
) {
patch(node)
node.position.start = child.position.start
}
if (
child.position &&
index === siblings.length - 1 &&
(!node.position || !node.position.end)
) {
patch(node)
node.position.end = child.position.end
}
}
)
/**
* @param {Node} node
* @returns {asserts node is Node & {position: Position}}
*/
function patch(node) {
if (!node.position) {
// @ts-expect-error: fine, well fill it later.
node.position = {}
}
}

View File

@@ -0,0 +1,3 @@
export const removeEmptyNodes: import("../../node_modules/unist-util-modify-children/lib/index.js").Modify<import("nlcst").Paragraph | import("nlcst").Root>;
export type Paragraph = import('nlcst').Paragraph;
export type Root = import('nlcst').Root;

View File

@@ -0,0 +1,23 @@
/**
* @typedef {import('nlcst').Paragraph} Paragraph
* @typedef {import('nlcst').Root} Root
*/
import {modifyChildren} from 'unist-util-modify-children'
// Remove empty children.
export const removeEmptyNodes = modifyChildren(
/**
* @type {import('unist-util-modify-children').Modifier<Paragraph | Root>}
*/
function (child, index, parent) {
if ('children' in child && child.children.length === 0) {
parent.children.splice(index, 1)
// Next, iterate over the node *now* at the current position (which was the
// next node).
return index
}
}
)

22
node_modules/parse-latin/license generated vendored Normal file
View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2014 Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

103
node_modules/parse-latin/package.json generated vendored Normal file
View File

@@ -0,0 +1,103 @@
{
"name": "parse-latin",
"version": "7.0.0",
"description": "Latin-script (natural language) parser",
"license": "MIT",
"keywords": [
"nlcst",
"latin",
"script",
"natural",
"language",
"parser"
],
"repository": "wooorm/parse-latin",
"bugs": "https://github.com/wooorm/parse-latin/issues",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
},
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"sideEffects": false,
"type": "module",
"exports": "./index.js",
"files": [
"lib/",
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/nlcst": "^2.0.0",
"@types/unist": "^3.0.0",
"nlcst-to-string": "^4.0.0",
"unist-util-modify-children": "^4.0.0",
"unist-util-visit-children": "^3.0.0",
"vfile": "^6.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/regenerate": "^1.0.0",
"@unicode/unicode-15.0.0": "^1.0.0",
"c8": "^8.0.0",
"is-hidden": "^2.0.0",
"nlcst-test": "^4.0.0",
"prettier": "^3.0.0",
"regenerate": "^1.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"unist-util-remove-position": "^5.0.0",
"xo": "^0.55.0"
},
"scripts": {
"prepack": "npm run generate && npm run build && npm run format",
"generate": "node script/build-expressions.js",
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"bracketSpacing": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"remark-preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"ignoreCatch": true,
"strict": true
},
"xo": {
"overrides": [
{
"files": [
"script/**/*.js",
"test/**/*.js"
],
"rules": {
"no-await-in-loop": "off"
}
}
],
"prettier": true,
"rules": {
"max-depth": "off",
"no-misleading-character-class": "off",
"unicorn/prefer-at": "off"
}
}
}

226
node_modules/parse-latin/readme.md generated vendored Normal file
View File

@@ -0,0 +1,226 @@
# parse-latin
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Size][size-badge]][size]
Natural language parser, for Latin-script languages, that produces [nlcst][].
## Contents
* [What is this?](#what-is-this)
* [When should I use this?](#when-should-i-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`ParseLatin()`](#parselatin)
* [Algorithm](#algorithm)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package exposes a parser that takes Latin-script natural language and
produces a syntax tree.
## When should I use this?
If you want to handle natural language as syntax trees manually, use this.
Alternatively, you can use the retext plugin [`retext-latin`][retext-latin],
which wraps this project to also parse natural language at a higher-level
(easier) abstraction.
Whether Old-English (“þā gewearþ þǣm hlāforde and þǣm hȳrigmannum wiþ ānum
penninge”), Icelandic (“Hvað er að frétta”), French (“Où sont les toilettes?”),
this project does a good job at tokenizing it.
For English and Dutch, you can instead use [`parse-english`][parse-english] and
[`parse-dutch`][parse-dutch].
You can somewhat use this for Latin-like scripts, such as Cyrillic (“привет”),
Georgian (“გამარჯობა”), Armenian (“Բարեւ”), and such.
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install parse-latin
```
In Deno with [`esm.sh`][esmsh]:
```js
import {ParseLatin} from 'https://esm.sh/parse-latin@7'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import {ParseLatin} from 'https://esm.sh/parse-latin@7?bundle'
</script>
```
## Use
```js
import {ParseLatin} from 'parse-latin'
import {inspect} from 'unist-util-inspect'
const tree = new ParseLatin().parse('A simple sentence.')
console.log(inspect(tree))
```
Yields:
```txt
RootNode[1] (1:1-1:19, 0-18)
└─0 ParagraphNode[1] (1:1-1:19, 0-18)
└─0 SentenceNode[6] (1:1-1:19, 0-18)
├─0 WordNode[1] (1:1-1:2, 0-1)
│ └─0 TextNode "A" (1:1-1:2, 0-1)
├─1 WhiteSpaceNode " " (1:2-1:3, 1-2)
├─2 WordNode[1] (1:3-1:9, 2-8)
│ └─0 TextNode "simple" (1:3-1:9, 2-8)
├─3 WhiteSpaceNode " " (1:9-1:10, 8-9)
├─4 WordNode[1] (1:10-1:18, 9-17)
│ └─0 TextNode "sentence" (1:10-1:18, 9-17)
└─5 PunctuationNode "." (1:18-1:19, 17-18)
```
## API
This package exports the identifier [`ParseLatin`][api-parse-latin].
There is no default export.
### `ParseLatin()`
Create a new parser.
#### `ParseLatin#parse(value)`
Turn natural language into a syntax tree.
###### Parameters
* `value` (`string`, optional)
— value to parse
###### Returns
Tree ([`RootNode`][root]).
## Algorithm
> 👉 **Note**:
> The easiest way to see how `parse-latin` parses, is by using the
> [online parser demo][demo], which shows the syntax tree corresponding to
> the typed text.
`parse-latin` splits text into white space, punctuation, symbol, and word
tokens:
* “word” is one or more unicode letters or numbers
* “white space” is one or more unicode white space characters
* “punctuation” is one or more unicode punctuation characters
* “symbol” is one or more of anything else
Then, it manipulates and merges those tokens into a syntax tree, adding
sentences and paragraphs where needed.
* some punctuation marks are part of the word they occur in, such as
`non-profit`, `shes`, `G.I.`, `11:00`, `N/A`, `&c`, `nineteenth- and…`
* some periods do not mark a sentence end, such as `1.`, `e.g.`, `id.`
* although periods, question marks, and exclamation marks (sometimes) end a
sentence, that end might not occur directly after the mark, such as `.)`,
`."`
* …and many more exceptions
## Types
This package is fully typed with [TypeScript][].
It exports no additional types.
## Compatibility
Projects maintained by me are compatible with maintained versions of Node.js.
When I cut a new major release, I drop support for unmaintained versions of
Node.
This means I try to keep the current release line, `parse-latin@^7`, compatible
with Node.js 16.
## Security
This package is safe.
## Related
* [`parse-english`](https://github.com/wooorm/parse-english)
— English (natural language) parser
* [`parse-dutch`](https://github.com/wooorm/parse-dutch)
— Dutch (natural language) parser
## Contribute
Yes please!
See [How to Contribute to Open Source][contribute].
## License
[MIT][license] © [Titus Wormer][author]
<!-- Definitions -->
[build-badge]: https://github.com/wooorm/parse-latin/workflows/main/badge.svg
[build]: https://github.com/wooorm/parse-latin/actions
[coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/parse-latin.svg
[coverage]: https://codecov.io/github/wooorm/parse-latin
[downloads-badge]: https://img.shields.io/npm/dm/parse-latin.svg
[downloads]: https://www.npmjs.com/package/parse-latin
[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=parse-latin
[size]: https://bundlejs.com/?q=parse-latin
[npm]: https://docs.npmjs.com/cli/install
[demo]: https://wooorm.com/parse-latin/
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[typescript]: https://www.typescriptlang.org
[contribute]: https://opensource.guide/how-to-contribute/
[license]: license
[author]: https://wooorm.com
[nlcst]: https://github.com/syntax-tree/nlcst
[root]: https://github.com/syntax-tree/nlcst#root
[retext-latin]: https://github.com/retextjs/retext/tree/main/packages/retext-latin
[parse-english]: https://github.com/wooorm/parse-english
[parse-dutch]: https://github.com/wooorm/parse-dutch
[api-parse-latin]: #parselatin