{"id":51323893,"url":"https://github.com/phax/jcrepdl","last_synced_at":"2026-07-01T16:03:08.164Z","repository":{"id":362882107,"uuid":"1261137113","full_name":"phax/jcrepdl","owner":"phax","description":"Java implementation of CREPDL","archived":false,"fork":false,"pushed_at":"2026-06-13T11:54:26.000Z","size":844,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T13:22:56.624Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T09:32:50.000Z","updated_at":"2026-06-13T11:54:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/phax/jcrepdl","commit_stats":null,"previous_names":["phax/jcrepdl"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/phax/jcrepdl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fjcrepdl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fjcrepdl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fjcrepdl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fjcrepdl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phax","download_url":"https://codeload.github.com/phax/jcrepdl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fjcrepdl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35013184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-01T16:03:07.504Z","updated_at":"2026-07-01T16:03:08.153Z","avatar_url":"https://github.com/phax.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jcrepdl\n\n\u003c!-- ph-badge-start --\u003e\n[![Sonatype Central](https://maven-badges.sml.io/sonatype-central/com.helger.crepdl/jcrepdl/badge.svg)](https://maven-badges.sml.io/sonatype-central/com.helger.crepdl/jcrepdl/)\n[![javadoc](https://javadoc.io/badge2/com.helger.crepdl/jcrepdl/javadoc.svg)](https://javadoc.io/doc/com.helger.crepdl/jcrepdl)\n\n\u003e If this project saved you some time or made your day a little easier, a star would mean a lot \u0026mdash; it helps others find it too.\n\u003c!-- ph-badge-end --\u003e\n\nA Java 17 implementation of **ISO/IEC 19757-7** \u0026mdash; the *Character Repertoire\nDescription Language* (CREPDL).\n\nThis is a from-scratch Java port of the F# reference implementation by\n**MURATA Makoto / CITPC** (https://github.com/CITPCSHARE/CREPDL, branch\n`Version2`).  The library can parse CREPDL XML scripts, expand them, and\nevaluate arbitrary characters / grapheme clusters / strings / streams against\nthem, returning a three-valued result (`TRUE`, `FALSE`, `UNKNOWN`).\n\n## Maven usage\n\nAdd the following to your `pom.xml`, where `x.y.z` is the latest released version:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.crepdl\u003c/groupId\u003e\n  \u003cartifactId\u003ejcrepdl\u003c/artifactId\u003e\n  \u003cversion\u003ex.y.z\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n---\n\n## Table of contents\n\n1.  [What is CREPDL?](#1-what-is-crepdl)\n2.  [The CREPDL XML vocabulary](#2-the-crepdl-xml-vocabulary)\n3.  [The three-valued result](#3-the-three-valued-result)\n4.  [Character vs. grapheme-cluster mode](#4-character-vs-grapheme-cluster-mode)\n5.  [Registries of predefined repertoires](#5-registries-of-predefined-repertoires)\n6.  [What this project does](#6-what-this-project-does)\n7.  [Project architecture](#7-project-architecture)\n8.  [Bundled data](#8-bundled-data)\n9.  [Build, install, dependency](#9-build-install-dependency)\n10. [Public Java API](#10-public-java-api)\n11. [Example CREPDL scripts](#11-example-crepdl-scripts)\n12. [The benchmark](#12-the-benchmark)\n13. [Testing and the corpus suite](#13-testing-and-the-corpus-suite)\n14. [Limitations](#14-limitations)\n15. [Security considerations](#15-security-considerations)\n16. [Differences from the F# reference](#16-differences-from-the-f-reference)\n17. [License](#17-license)\n18. [News and noteworthy](#18-news-and-noteworthy)\n\n---\n\n## 1. What is CREPDL?\n\n**CREPDL** is part 7 of ISO/IEC 19757 *Document Schema Definition Languages\n(DSDL)*.  It is an XML vocabulary for describing a **character repertoire**:\nthe set of Unicode characters (or grapheme clusters) that a class of\ndocuments is allowed to contain.\n\nA CREPDL script answers one question for any given input character `c`:\n\n\u003e *Is `c` a member of the described repertoire?*\n\nThe answer is one of three values:\n\n| Result    | Meaning                                                                                          |\n| --------- | ------------------------------------------------------------------------------------------------ |\n| `TRUE`    | `c` is definitely in the repertoire                                                              |\n| `FALSE`   | `c` is definitely not in the repertoire                                                          |\n| `UNKNOWN` | The script's lower bound (kernel) does not include `c` and the upper bound (hull) does include `c` |\n\nWhy three values? Because CREPDL scripts can describe repertoires\napproximately, using a tight lower bound (the **kernel**, e.g. a precise regex\nof allowed characters) and a loose upper bound (the **hull**, e.g. a wider set\nthat surely contains the kernel). Characters that fall in the gap between\nkernel and hull can neither be confirmed nor rejected; they are *unknown*.\n\nCREPDL is XML namespace `http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0`.\nThis library handles **version 2.0 only**; v1.0 documents are rejected at\nparse time with an \"Illegal namespace\" error.\n\n---\n\n## 2. The CREPDL XML vocabulary\n\nA CREPDL script's root element must be one of six concrete elements. Every\nelement accepts three optional common attributes that govern its scope.\n\n### 2.1 Common attributes\n\n| Attribute       | On every CREPDL element | Meaning                                                                                                  |\n| --------------- | :---------------------: | -------------------------------------------------------------------------------------------------------- |\n| `mode`          | yes                     | `\"character\"` (default) or `\"graphemeCluster\"`. Only the root mode is honoured; nested ones are advisory. |\n| `minUcsVersion` | yes                     | Encoded version like `\"5.2\"`. The lower bound of the Unicode version window.                              |\n| `maxUcsVersion` | yes                     | Encoded version like `\"17.0\"`. The upper bound of the Unicode version window.                             |\n\nVersions are internally encoded as `major*10000 + minor*100 + patch`\n(see `CCREPDL.versionString2Int(String)`). The defaults are `2.0` and `17.0`.\n\n### 2.2 Element reference\n\n| Element                 | Children                                            | Purpose                                                                                                                                                                                                                            |\n| ----------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `\u003cunion\u003e`               | zero or more CREPDL elements                        | Set union of children.  Empty union matches nothing.                                                                                                                                                                              |\n| `\u003cintersection\u003e`        | zero or more CREPDL elements                        | Set intersection.  Empty intersection matches everything.                                                                                                                                                                          |\n| `\u003cdifference\u003e`          | one or more CREPDL elements                         | First child minus the union of the remaining children.                                                                                                                                                                            |\n| `\u003cref href=\"URL\"/\u003e`     | none in source; one after expansion                 | Reference to another CREPDL script. The expander loads the script (relative to the document URI) and inlines its root. Self-reference / cycles raise a parse error.                                                                |\n| `\u003crepertoire registry=… name=… number=… version=…/\u003e` | no CREPDL children                                  | Names a predefined collection from one of four registries: `10646`, `IANA`, `IVD`, `CLDR`.                                                                                                                                       |\n| `\u003cchar\u003e` *regex source* | optional `\u003ckernel\u003e`+`\u003chull\u003e` sub-elements           | An ICU-flavoured regex (treated as a Java `Pattern` with `COMMENTS \\| UNICODE_CHARACTER_CLASS` flags). Plain text content sets both kernel and hull to the same pattern; otherwise the children supply kernel and/or hull separately. |\n\n### 2.3 Set-operation semantics\n\nThe set operations are three-valued. Letting *X, Y* be three-valued\nmembership of an element in two child repertoires, the rules are:\n\n`union(X, Y)`\n\n| X \\ Y | T | F | U |\n|-------|---|---|---|\n| T     | T | T | T |\n| F     | T | F | U |\n| U     | T | U | U |\n\n`intersection(X, Y)`\n\n| X \\ Y | T | F | U |\n|-------|---|---|---|\n| T     | T | F | U |\n| F     | F | F | F |\n| U     | U | F | U |\n\n`difference(first, ∪(rest))`: only `TRUE` if the first child says `TRUE`\nand the rest says `FALSE`. Other combinations collapse to `FALSE` or\n`UNKNOWN` per the standard table.\n\n### 2.4 The `\u003cchar\u003e` element\n\n`\u003cchar\u003e` describes a regex (or a set of code-point literals; see [\u0026sect;2.5](#25-char-content-dialects-regex-vs-code-point-literal))\nover a single character or grapheme cluster. There are four legal shapes:\n\n```xml\n\u003c!-- (1) Plain regex: both kernel and hull are this pattern --\u003e\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e[A-Z]\u003c/char\u003e\n\n\u003c!-- (2) Kernel only: matching characters are TRUE, others UNKNOWN --\u003e\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003ckernel\u003e[A-Z]\u003c/kernel\u003e\n\u003c/char\u003e\n\n\u003c!-- (3) Hull only: matching characters are UNKNOWN, others FALSE --\u003e\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003chull\u003e[A-Z]\u003c/hull\u003e\n\u003c/char\u003e\n\n\u003c!-- (4) Both: classical three-valued shape. Matching kernel -\u003e TRUE;\n     not matching hull -\u003e FALSE; in between -\u003e UNKNOWN. --\u003e\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003ckernel\u003e[A-Z]\u003c/kernel\u003e\n  \u003chull\u003e[A-Za-z]\u003c/hull\u003e\n\u003c/char\u003e\n```\n\n### 2.5 `\u003cchar\u003e` content dialects: regex vs. code-point literal\n\nTwo ISO/IEC 19757-7 revisions disagree on what the text inside `\u003cchar\u003e`,\n`\u003ckernel\u003e`, and `\u003chull\u003e` means while sharing the same XML namespace:\n\n| Revision | `\u003cchar\u003e` content syntax | Example |\n| --- | --- | --- |\n| 2014-era v2.0 (F# reference) | ICU regular expression                                           | `[A-Z]`, `\\p{Mn}`, `[\\x{0020}-\\x{007E}]` |\n| ISO/IEC 19757-7:2020         | Whitespace-separated code-point literals and ranges, *no regex* | `U+0020`, `U+0020-U+007E`, `U+0009 U+000A U+0020-U+007E` |\n\nThis library transparently supports **both dialects**:\n\n- If the trimmed text content matches the code-point-literal pattern\n  (one or more whitespace-separated tokens, each either `U+HEX` or\n  `U+HEX-U+HEX` with values in `0..0x10FFFF`, ranges non-reversed), it is\n  translated at parse time into the equivalent Java regex character class\n  (e.g. `U+0020-U+007E` \u0026rarr; `[\\x{0020}-\\x{007E}]`).\n- Otherwise, the content is used verbatim as a Java regex (the original\n  v2.0 behaviour).\n\nThe detection is unambiguous: a literal regex containing only `U+XXXX`\ntokens cannot match a single Unicode character anyway (it would try to\nmatch the 6-character string `\"U+0020\"`), so the new interpretation is\nstrictly better. All existing regex scripts continue to work unchanged.\n\nDetection is implemented by `CodePointSyntax.toRegexOrNull(String)`.\n\nDecision table for `check(s, char)`:\n\n| kernel | hull | kernel matches `s` | hull matches `s` | result    |\n| ------ | ---- | ------------------ | ---------------- | --------- |\n| set    | -    | yes                | -                | `TRUE`    |\n| set    | -    | no                 | -                | `UNKNOWN` |\n| -      | set  | -                  | yes              | `UNKNOWN` |\n| -      | set  | -                  | no               | `FALSE`   |\n| set    | set  | yes                | yes              | `TRUE`    |\n| set    | set  | no                 | yes              | `UNKNOWN` |\n| set    | set  | no                 | no               | `FALSE`   |\n| set    | set  | yes                | no               | `TRUE` (kernel implies hull was supposed to match too) |\n\n---\n\n## 3. The three-valued result\n\nInternally the library carries an `EThreeValuedBoolean` (package-private\nhelpers `union`, `intersection`) and the API surface returns the equivalent\n`ECREPDLValidationResult` (`TRUE` / `FALSE` / `UNKNOWN`) with\n`isTrue()` / `isFalse()` / `isUnknown()` convenience checks.\n\nFor *stream* validation (`validateString`, `validateTextStream`,\n`validateFile`), the library returns a `CREPDLStreamValidationResult` record\nwith two `String[]` arrays:\n\n- `unknowns()` \u0026mdash; units that evaluated to `UNKNOWN`\n- `notIncluded()` \u0026mdash; units that evaluated to `FALSE`\n\nUnits that evaluated to `TRUE` are not collected.\n\n---\n\n## 4. Character vs. grapheme-cluster mode\n\nThe CREPDL root element's `mode` attribute determines how the input stream\nis **broken into evaluation units**:\n\n| Mode               | Unit                                                                                                                                  |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |\n| `character`        | A single Unicode code point (one Java `char`, or a surrogate pair as a two-`char` string). Implemented by `CharacterEnumerator`.       |\n| `graphemeCluster`  | A user-perceived \"character\" per Unicode UAX #29, e.g. base + combining marks + emoji ZWJ sequences. Uses `java.text.BreakIterator`. |\n\nIf a validator is built for one mode and the wrong API is called\n(`validateCharacter` on a grapheme-cluster script, or vice versa),\n`IllegalStateException` is thrown.\n\n---\n\n## 5. Registries of predefined repertoires\n\n`\u003crepertoire\u003e` references one of four named registries:\n\n### 5.1 `registry=\"10646\"` \u0026mdash; ISO/IEC 10646 collections\n\nSelected by `@name` (e.g. `\"BASIC LATIN\"`, `\"BMP\"`, `\"Age10-0\"`,\n`\"IICORE\"`) or by `@number` (e.g. `1`, `300`, `1000`, `370`).\n\nThe library ships with three internal indexes derived from the upstream\ncollection definitions:\n\n| Index           | Storage                                                                                                       | Count |\n| --------------- | ------------------------------------------------------------------------------------------------------------- | ----: |\n| Inline          | One line per collection in `iso10646-inline.txt`, range list                                                  |   323 |\n| Out-of-line     | A separate bundled `.txt` file of hex code points per range (e.g. `IICORE.txt`, `Age10-0.txt`, `Age17-0.txt`) |    44 |\n| CREPDL-script   | A bundled XML fragment that defines the collection via `\u003cunion\u003e`/`\u003cintersection\u003e`/`\u003cdifference\u003e` of others    |    35 |\n\nLookups go through `ISO10646Collections.lookupInlineByName/Number`,\n`lookupOutOfLineResource`, `lookupCREPDLScriptResource`.\n\n### 5.2 `registry=\"IANA\"` \u0026mdash; IANA charsets\n\n`\u003crepertoire registry=\"IANA\" name=\"ISO-8859-1\"/\u003e` is implemented by a\nround-trip through `java.nio.charset.Charset`: a candidate string is in\nthe repertoire iff `decode(encode(s))` equals `s` exactly (no\nunmappable / malformed input).  The `name` is the JVM charset name; the\nIANA *MIB enumeration* (`number`) is **not** supported (matching the F#\nreference).\n\n### 5.3 `registry=\"IVD\"` \u0026mdash; Ideographic Variation Database\n\n`\u003crepertoire registry=\"IVD\" name=\"Adobe-Japan1\"/\u003e` validates 3-or-4-char\nstrings of the form *base + variation selector* against the bundled\n`IVD_Sequences.txt`.  Valid variation selectors are\n`U+E0100..U+E0120`.\n\n### 5.4 `registry=\"CLDR\"`\n\nRecognised by the parser but **not implemented**.  Building a validator\nthat references CLDR throws `CREPDLParseException(\"CLDR is not supported\nyet\")`, matching the F# reference.\n\n---\n\n## 6. What this project does\n\nA Java 17 implementation that reads CREPDL XML, expands it (resolving\n`\u003cref\u003e` chains and CREPDL-defined ISO 10646 collections), pre-builds\nrepertoire dictionaries, and answers per-character / per-string queries\nwith three-valued results.\n\nWhat was ported, broken down by concern:\n\n| Concern                                  | Java type                                                                                                                       |\n| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| Namespace, attribute, mode literals      | `CCREPDL`                                                                                                                       |\n| Modes (CHARACTER / GRAPHEME_CLUSTER)     | `EMode`                                                                                                                         |\n| Internal 3-valued logic                  | `EThreeValuedBoolean` (with `union` / `intersection` lazy ops)                                                                  |\n| Public 3-valued result                   | `ECREPDLValidationResult`                                                                                                       |\n| Sealed CREPDL node hierarchy             | `ICREPDLNode` + records `CREPDLUnion`, `CREPDLIntersection`, `CREPDLDifference`, `CREPDLRef`, `CREPDLRepertoire`, `CREPDLChar` |\n| Sealed registry hierarchy                | `IRegistry` + records `RegistryISO10646`, `RegistryCLDR`, `RegistryIANA`, `RegistryIVD`                                         |\n| XML parser                               | `CREPDLReader` (validates namespace, element/attribute set, content model)                                                      |\n| Ref + ISO10646-collection expander       | `RefAndRepertoireExpander` (with cycle detection on both axes)                                                                  |\n| Registry \u0026rarr; Repertoire dictionary    | `RegistryRepertoireDictionary`                                                                                                  |\n| Code-point range repertoire              | `CodePointRangeRepertoire` (sorted binary search; hex/decimal parser, BOM-strip)                                                |\n| ISO 10646 collection lookup tables       | `ISO10646Collections`                                                                                                           |\n| IANA charset round-trip                  | `IANARepertoires`                                                                                                               |\n| IVD bench against `IVD_Sequences.txt`    | `IVDRepertoires`                                                                                                                |\n| Regex evaluator                          | `CharMatcher` (Java `Pattern` with `COMMENTS \\| UNICODE_CHARACTER_CLASS`)                                                       |\n| 2020 code-point-literal `\u003cchar\u003e` content | `CodePointSyntax` (translates `U+XXXX` / `U+XXXX-U+YYYY` to a Java regex character class)                                       |\n| Three-valued evaluator                   | `StringChecker`                                                                                                                 |\n| Character iterator                       | `CharacterEnumerator`                                                                                                           |\n| Grapheme-cluster iterator                | `GraphemeClusterEnumerator` (`java.text.BreakIterator`)                                                                         |\n| Public facade                            | `CREPDLValidator`                                                                                                               |\n\n---\n\n## 7. Project architecture\n\n```\ncom.helger.crepdl\n├── CCREPDL                          // namespace, element names, attribute names, default versions\n├── EMode                            // CHARACTER, GRAPHEME_CLUSTER\n├── ECREPDLValidationResult          // TRUE, FALSE, UNKNOWN  (public)\n├── EThreeValuedBoolean              // TRUE, FALSE, UNKNOWN  (internal, with union/intersection helpers)\n│\n├── model/                           // immutable sealed AST records\n│   ├── ICREPDLNode  (sealed)\n│   ├── CREPDLUnion, CREPDLIntersection, CREPDLDifference\n│   ├── CREPDLRef, CREPDLRepertoire, CREPDLChar\n│   ├── IRegistry  (sealed)\n│   └── RegistryISO10646, RegistryCLDR, RegistryIANA, RegistryIVD\n│\n├── parse/                           // XML -\u003e AST\n│   ├── CREPDLReader                 // DOM-based, namespace + attribute + element validation\n│   └── CREPDLParseException\n│\n├── repertoire/                      // concrete repertoires\n│   ├── IRepertoire                  // String -\u003e EThreeValuedBoolean\n│   ├── CodePointRangeRepertoire     // binary search over sorted ranges\n│   ├── ISO10646Collections          // lookup tables (inline / out-of-line / CREPDL-script)\n│   ├── IANARepertoires              // round-trip via java.nio.charset\n│   └── IVDRepertoires               // IVD_Sequences.txt\n│\n├── validate/                        // pipeline + facade\n│   ├── RefAndRepertoireExpander     // \u003cref\u003e + CREPDL-script collection expansion (cycle-safe)\n│   ├── RegistryRepertoireDictionary // pre-builds one IRepertoire per IRegistry in the tree\n│   ├── CharMatcher                  // compiles + evaluates a CREPDLChar against a candidate\n│   ├── StringChecker                // three-valued tree walk\n│   ├── CharacterEnumerator          // BMP + surrogate-pair iteration\n│   ├── GraphemeClusterEnumerator    // UAX #29 cluster iteration\n│   ├── CREPDLStreamValidationResult // (unknowns[], notIncluded[]) record\n│   └── CREPDLValidator              // PUBLIC FACADE\n│\n└── benchmark/\n    └── CREPDLBenchmark              // runnable main: lookups + verifications + coverage report\n```\n\nValidation pipeline:\n\n```\n   parse XML  -\u003e  expand \u003cref\u003e and CREPDL-script \u003crepertoire\u003e  -\u003e  build registry-repertoire cache\n       |                          |                                            |\n       v                          v                                            v\nCREPDLReader            RefAndRepertoireExpander         RegistryRepertoireDictionary\n       \\________________________ |  _____________________________/\n                                 v\n                          CREPDLValidator\n                                 |\n                                 v\n                          StringChecker (3-valued tree walk)\n                                 |\n                                 v\n                          CharMatcher / IRepertoire\n```\n\n---\n\n## 8. Bundled data\n\nThe bundled resources are organised by **registry** under\n`src/main/resources/external/crepdl/repo/`:\n\n```\nexternal/crepdl/repo/\n├── 10646/                            ISO/IEC 10646 collections\n│   ├── inline.txt                    323 collections as one number|name|ranges line each\n│   ├── outofline.txt                 44-entry index of out-of-line range files\n│   ├── crepdl-index.txt              35-entry index of CREPDL-script collections\n│   ├── crepdl/                       35 small CREPDL XML scripts\n│   │   └── crepdl_*.xml              (e.g. UNICODE 3.1 = union of Age1-1..Age3-1)\n│   ├── Age*.txt                      Code points first assigned in each Unicode version\n│   │                                  (Age1-1, Age2-0, …, Age16-0, Age17-0)\n│   ├── IICORE.txt                    Bundled hex-range files for IICORE,\n│   ├── JIExt.txt                     Japanese ideographics extensions\n│   ├── JAPANESE *.txt                etc., one file per collection number\n│   ├── JapaneseCoreKanji.txt\n│   ├── 281.txt / 282.txt             MES-1, MES-2\n│   ├── 286.txt / 288.txt             other numbered collections\n│   ├── 301.txt / 302.txt             BMP-AMD.7, BMP SECOND EDITION\n│   ├── -100285.txt / -200285.txt     synthetic sub-pieces of BASIC JAPANESE\n│   ├── -287.txt / -340.txt           synthetic sub-pieces\n└── IVD/                              Ideographic Variation Database\n    └── IVD_Sequences.txt             (1.3 MB) base + variation selector pairs\n```\n\n| File / directory                  | Source                                                  | Purpose                                                                  |\n| --------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------ |\n| `10646/inline.txt`                | extracted from the upstream collection definitions      | 323 collections as one `number\\|name\\|ranges` line each                  |\n| `10646/outofline.txt`             | extracted index                                         | 44 collections mapped to bundled hex range files                         |\n| `10646/crepdl-index.txt`          | extracted index                                         | 35 collections mapped to bundled CREPDL XML fragment files (incl. UNICODE 12.0 \u0026hellip; 17.0) |\n| `10646/crepdl/`                   | extracted XML fragments                                 | 35 small CREPDL XML scripts (e.g. UNICODE 3.1 = union of Age1-1..Age3-1) |\n| `10646/Age*.txt`                  | derived from `DerivedAge.txt`                           | Code points first assigned in each Unicode age, hex `0x...,0x...`        |\n| `10646/IICORE.txt`, `JIExt.txt`, `JAPANESE *.txt`, etc. | copied verbatim from upstream `CREPDL/*.txt` | The actual code-point ranges in hex                          |\n| `IVD/IVD_Sequences.txt` (1.3 MB)  | copied verbatim from upstream `CREPDL/IVD_Sequences.txt` | IVD base + variation-selector pairs per collection                       |\n\nTotal: 83 bundled resource files (47 directly under `10646/`, 35 under\n`10646/crepdl/`, 1 under `IVD/`).\n\nThe extraction from the upstream collection definitions is mechanical\n\u0026mdash; the upstream source has three list literals\n(`inLineCollections`, `outOfLineCollections`, `collectionsInCREPDL`) and\nan offline Python script walks them, normalising hex ranges to\ndecimal-pair semicolon lists for the inline file and copying the rest\nverbatim.\n\n---\n\n## 9. Build, install, dependency\n\n### Requirements\n\n- **JDK 17** or newer (uses sealed interfaces, records, pattern matching for `instanceof`). CI matrix: 17, 21, 25.\n- **Maven 3.9+**.\n\n### Building\n\n```bash\nmvn clean install\n```\n\nThis runs the test suite (152 tests, see [\u0026sect;13](#13-testing-and-the-corpus-suite))\nand also runs the `forbiddenapis` Maven plugin (`check` + `testCheck` goals)\nto forbid `jdk-unsafe`, `jdk-deprecated`, `jdk-internal`, `jdk-non-portable`,\n`jdk-system-out`, and `jdk-reflection` API usage.\n\n### Maven coordinates\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.crepdl\u003c/groupId\u003e\n  \u003cartifactId\u003ejcrepdl\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Runtime dependencies\n\n| Artifact                              | Why                                                  |\n| ------------------------------------- | ---------------------------------------------------- |\n| `com.helger.commons:ph-base`          | `IHasID`, `EnumHelper`, `NonBlockingBufferedReader`, `StringHelper` |\n| `com.helger.commons:ph-annotations`   | (transitive of `ph-base`) `com.helger.annotation.concurrent.Immutable` |\n| `org.jspecify:jspecify`               | `@NonNull` / `@Nullable` nullness annotations         |\n| `org.slf4j:slf4j-api`                 | (transitive) logging                                  |\n\nXML parsing uses only the JDK (`javax.xml.parsers.DocumentBuilderFactory`)\n\u0026mdash; no `ph-xml` / `ph-commons` dependency.\n\n---\n\n## 10. Public Java API\n\nThe single entry point is **`CREPDLValidator`**. It accepts a script from\nany of five sources, expands it eagerly, and pre-builds repertoire\ndictionaries. Subsequent per-string validations are O(1) lookups against a\n3000-entry cache.\n\n### 10.1 Constructing a validator\n\n```java\nimport com.helger.crepdl.validate.CREPDLValidator;\nimport com.helger.crepdl.model.ICREPDLNode;\nimport com.helger.crepdl.parse.CREPDLReader;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.io.Reader;\nimport java.io.InputStream;\n\n// From a parsed AST\nICREPDLNode aRoot = CREPDLReader.readScriptFromString (sXml, null);\nCREPDLValidator a1 = CREPDLValidator.create (aRoot);\n\n// From a local file\nCREPDLValidator a2 = CREPDLValidator.create (new File (\"scripts/BasicLatin.crepdl\"));\n\n// From a URI (http: or file:)\nCREPDLValidator a3 = CREPDLValidator.create (URI.create (\"file:///path/to/script.crepdl\"));\n\n// From an in-memory string\nCREPDLValidator a4 = CREPDLValidator.createFromString (sXml, URI.create (\"base:/\"));\n\n// From a Reader or InputStream (use CREPDLReader directly)\nCREPDLValidator a5 = CREPDLValidator.create (CREPDLReader.readScript (aReader, null));\n```\n\nEvery `create(...)` overload above uses `DenyAllRefResolver` \u0026mdash; scripts\ncontaining `\u003cref href=\"...\"/\u003e` will fail to build. To allow refs, pass an\nexplicit `ICREPDLRefResolver` (see [\u0026sect;11.6](#116-composing-via-ref) and\n[\u0026sect;15.2](#152-what-is-hardened) for the bundled resolvers).\n\n### 10.2 Validating a single character\n\n```java\nimport com.helger.crepdl.ECREPDLValidationResult;\n\nECREPDLValidationResult e = aValidator.validateCharacter (\"A\");\nswitch (e)\n{\n  case TRUE    -\u003e System.out.println (\"in the repertoire\");\n  case FALSE   -\u003e System.out.println (\"not in the repertoire\");\n  case UNKNOWN -\u003e System.out.println (\"kernel and hull disagree\");\n}\n```\n\nPass a 2-`char` string for supplementary code points: e.g.\n`aValidator.validateCharacter (new String (Character.toChars (0x1F30D)))`\n\u0026mdash; the earth-globe emoji U+1F30D.\n\n`validateCharacter` throws `IllegalStateException` if the script's root mode\nis `graphemeCluster`. Use `validateGraphemeCluster(String)` in that case.\n\n### 10.3 Validating an entire string or stream\n\n```java\nimport com.helger.crepdl.validate.CREPDLStreamValidationResult;\n\nCREPDLStreamValidationResult res = aValidator.validateString (\"Hello, World!\");\nSystem.out.println (\"unknowns:     \" + java.util.Arrays.toString (res.unknowns ()));\nSystem.out.println (\"not included: \" + java.util.Arrays.toString (res.notIncluded ()));\n\n// or for any Reader (StringReader, FileReader, etc.)\nres = aValidator.validateTextStream (aReader);\n\n// or a file with an explicit charset name\nres = aValidator.validateFile (new File (\"input.txt\"), \"UTF-8\");\n```\n\nThe mode of stream iteration is governed by the root mode of the script.\n\n### 10.4 Inspecting the parsed model\n\nIf you need to walk the AST yourself (e.g. for analysis or transformation):\n\n```java\nICREPDLNode aNode = CREPDLReader.readScriptFromString (sXml, null);\nif (aNode instanceof CREPDLUnion aU)\n{\n  for (ICREPDLNode aChild : aU.children ())\n    ...\n}\n```\n\nThe model is fully sealed; the compiler will warn if a `switch` over\n`ICREPDLNode` misses a permitted subtype.\n\n---\n\n## 11. Example CREPDL scripts\n\nAll examples use the v2 namespace prefix `xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"`.\n\n### 11.1 A plain regex over BASIC LATIN\n\n```xml\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  [\\p{Alpha}]\n\u003c/char\u003e\n```\n\n### 11.1b The same script in ISO/IEC 19757-7:2020 code-point-literal syntax\n\n```xml\n\u003c!-- Equivalent to \u003cchar\u003e[\\x{0020}-\\x{007E}]\u003c/char\u003e --\u003e\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  U+0020-U+007E\n\u003c/char\u003e\n```\n\nA multi-range example (the SignalArc sample schema, accepted verbatim by\nthis library):\n\n```xml\n\u003cunion xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003cchar\u003eU+0009\u003c/char\u003e\n  \u003cchar\u003eU+000A\u003c/char\u003e\n  \u003cchar\u003eU+000D\u003c/char\u003e\n  \u003cchar\u003eU+0020-U+007E\u003c/char\u003e\n  \u003cchar\u003eU+00A0\u003c/char\u003e\n  \u003cchar\u003eU+00C0-U+00FF\u003c/char\u003e\n  \u003cchar\u003eU+2013-U+2014\u003c/char\u003e\n  \u003cchar\u003eU+2018-U+201E\u003c/char\u003e\n  \u003cchar\u003eU+2026\u003c/char\u003e\n\u003c/union\u003e\n```\n\n### 11.2 ISO 8859-1 by union\n\n```xml\n\u003cunion xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003crepertoire registry=\"10646\" number=\"1\"/\u003e     \u003c!-- BASIC LATIN --\u003e\n  \u003crepertoire registry=\"10646\" number=\"2\"/\u003e     \u003c!-- LATIN-1 SUPPLEMENT --\u003e\n\u003c/union\u003e\n```\n\nOr equivalently via IANA round-trip:\n\n```xml\n\u003crepertoire xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\n            registry=\"IANA\" name=\"ISO-8859-1\"/\u003e\n```\n\n### 11.3 BMP minus uppercase ASCII\n\n```xml\n\u003cdifference xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003crepertoire registry=\"10646\" number=\"300\"/\u003e   \u003c!-- BMP --\u003e\n  \u003cchar\u003e[A-Z]\u003c/char\u003e\n\u003c/difference\u003e\n```\n\n### 11.4 Kernel/hull approximation\n\n```xml\n\u003cchar xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003ckernel\u003e[\\p{IsHan}]\u003c/kernel\u003e\n  \u003chull\u003e[一-鿿]\u003c/hull\u003e\n\u003c/char\u003e\n```\n\n### 11.5 Adobe-Japan1 IVD collection, grapheme-cluster mode\n\n```xml\n\u003crepertoire xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\n            mode=\"graphemeCluster\"\n            registry=\"IVD\" name=\"Adobe-Japan1\"/\u003e\n```\n\n### 11.6 Composing via `\u003cref\u003e`\n\n```xml\n\u003c!-- Top-level script --\u003e\n\u003cunion xmlns=\"http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0\"\u003e\n  \u003cref href=\"basic-latin.crepdl\"/\u003e\n  \u003cref href=\"hiragana.crepdl\"/\u003e\n  \u003cref href=\"katakana.crepdl\"/\u003e\n\u003c/union\u003e\n```\n\n```java\n// Building the validator requires an explicit resolver because the\n// default DenyAllRefResolver would refuse the three \u003cref\u003e URIs above.\nimport com.helger.crepdl.validate.CREPDLValidator;\nimport com.helger.crepdl.validate.FileSystemRefResolver;\nimport java.nio.file.Path;\n\nCREPDLValidator v = CREPDLValidator.create (\n    Path.of (\"scripts/Top.crepdl\").toUri (),\n    new FileSystemRefResolver (Path.of (\"scripts\")));\n```\n\n`\u003cref\u003e` cycles are detected at expansion time and raise a parse error.\n\n---\n\n## 12. The benchmark\n\n`com.helger.crepdl.benchmark.CREPDLBenchmark` is a runnable main with three\nsections:\n\n### 12.1 Section 1 \u0026mdash; Lookups\n\nThroughput of `ISO10646Collections` lookups: by name, by number, for\ninline / out-of-line / CREPDL-script collections.\n\n### 12.2 Section 2 \u0026mdash; Verifications\n\n`validateCharacter` and `validateString` throughput for three\nrepresentative scripts (BASIC LATIN, BMP, Unicode 8.0), including\nchars/sec for a 13-char short string and a ~3.6 KB long string.\n\n### 12.3 Section 3 \u0026mdash; Coverage assessment for an arbitrary text\n\nFor the input text (default: `\"Hello, \u0026#19990;\u0026#30028; \u0026#x1f30d; \u0026mdash; Caf\u0026eacute; d\u0026eacute;j\u0026agrave;\"`), the\nbenchmark tries three \"scales\" of progressively larger predefined\nrepertoires:\n\n- **Block / plane scale**: BASIC LATIN \u0026rarr; ISO-8859-1 \u0026rarr; BMP \u0026rarr; BMP+SMP\n  \u0026rarr; BMP+SMP+SIP \u0026rarr; full UCS.\n- **Unicode age scale**: Unicode 3.1 \u0026rarr; 3.2 \u0026rarr; ... \u0026rarr; 17.0 (using the\n  bundled `Age*-*.txt` collections; covers every released Unicode\n  version up to and including 17.0).\n- **IANA charset scale**: US-ASCII \u0026rarr; ISO-8859-1 \u0026rarr; Shift_JIS \u0026rarr; UTF-8.\n\nFor each scale the benchmark reports per-candidate whether it covers the\ntext fully, and at the end prints the smallest covering candidate from\neach scale. This answers the question *\"what CREPDL range is needed for\nthis text?\"*.\n\n### 12.4 Running it\n\n```bash\nmvn -q exec:java \\\n  -Dexec.mainClass=com.helger.crepdl.benchmark.CREPDLBenchmark \\\n  -Dexec.args=\"your arbitrary text\"\n```\n\nSample output (default input):\n\n```\n=== [3] Coverage assessment for arbitrary text ===\n\n  Input: \"Hello, 世界 🌍 — Café déjà\"\n  Code-point count: 23, UTF-16 length: 24\n\n  Block / plane scale:\n    BASIC LATIN (U+0020..U+007E)         : not covered (7 char(s) outside)\n    ISO-8859-1 (Basic Latin + Latin-1)   : not covered (4 char(s) outside)\n    BMP (Plane 0)                        : not covered (1 char(s) outside)\n    BMP + SMP (Planes 0-1)               : COVERED\n    BMP + SMP + SIP (Planes 0-2)         : COVERED\n    Full UCS (Planes 0-2 + SSP)          : COVERED\n  ...\n  Recommendation:\n    Smallest block scale:        BMP + SMP (Planes 0-1)\n    Smallest Unicode age:        Unicode 6.0\n    Smallest IANA charset:       UTF-8\n```\n\n---\n\n## 13. Testing and the corpus suite\n\nThe library ships with **152 tests** across seven test classes:\n\n| Suite                      | Tests | What it exercises                                                                                                                       |\n| -------------------------- | ----: | --------------------------------------------------------------------------------------------------------------------------------------- |\n| `CCREPDLTest`              |     1 | The version-string encoding helper.                                                                                                     |\n| `CREPDLValidatorTest`      |    16 | All 6 element kinds, three-valued semantics, IANA, grapheme-cluster mode, stream validation, surrogate pairs, CREPDL-script collections. |\n| `CodePointLiteralCharTest` |     8 | The ISO/IEC 19757-7:2020 code-point-literal `\u003cchar\u003e` dialect (`U+XXXX`, `U+XXXX-U+YYYY`, regex fall-through).                            |\n| `CodePointSyntaxTest`      |     7 | The `CodePointSyntax` detection and translation helper in isolation.                                                                    |\n| `RefResolverTest`          |     7 | The `\u003cref\u003e` resolver SPI: deny-all default, file-system sandbox, traversal-escape rejection, non-file scheme rejection, end-to-end.      |\n| `CREPDLBenchmarkTest`      |     4 | End-to-end smoke for the benchmark main, plus sanity assertions on three sample inputs.                                                 |\n| `CREPDLScriptsCorpusTest`  |   109 | Walks the bundled `CREPDLScripts` corpus and runs one parameterised test per file.                                                      |\n\n### 13.1 The CREPDL Scripts corpus\n\n`src/test/resources/CREPDLScripts/examples/` is a byte-identical copy of\nthe upstream\n[CREPDLScripts](https://github.com/CITPCSHARE/CREPDLScripts)\nrepository, used as a real-world test corpus.\n\n| Corpus directory                       | Files | Expected outcome                                                                                |\n| -------------------------------------- | ----: | ----------------------------------------------------------------------------------------------- |\n| `version1/`                            |   46  | All rejected with `CREPDLParseException` (\"Illegal namespace\") \u0026mdash; v1 unsupported by design |\n| `version2/characterMode/`              |   46  | All parsed; 41 build a working validator                                                        |\n| `version2/graphemeClusterMode/`        |    9  | All parsed; 5 build a working validator                                                         |\n| `cjkvi/cjkvi-tables-master/`           |    4  | All rejected (v1)                                                                               |\n\n#### 13.2 Known v2 files that do not build a validator (documented)\n\n| File                  | Reason                                                                                                          |\n| --------------------- | --------------------------------------------------------------------------------------------------------------- |\n| `bmp.crepdl`          | Upstream XML is malformed: literal `mode=\"character\"\"` (stray double quote). JDK rejects; .NET XLinq tolerated. |\n| `namedSequenceTest.crepdl` | Same XML bug; also uses ICU `\\N{HIRAGANA LETTER A}` which `java.util.regex` does not support.              |\n| `8859-15b.crepdl`     | Uses `\u003crepertoire registry=\"IANA\" number=\"111\"/\u003e` (MIB enumeration) \u0026mdash; not supported by F# either.        |\n| `RefLoop.crepdl`      | Deliberate self-reference \u0026mdash; the cycle detector correctly throws.                                          |\n| `IVDfoo.crepdl`       | References a bogus IVD collection (upstream negative test).                                                     |\n\nThe corpus test explicitly enumerates these as expected failures and would\nfail if their behaviour changed.\n\n### 13.3 Running the tests\n\n```bash\nmvn test               # tests only\nmvn verify             # tests + forbidden-apis check + testCheck\n```\n\n---\n\n## 14. Limitations\n\n| Limitation                                                                                       | Workaround                                                       |\n| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |\n| Only CREPDL v2.0 namespace (`http://purl.oclc.org/dsdl/crepdl/ns/structure/2.0`).                | No v1 docs in scope; rewrite as v2.                              |\n| Inside the v2.0 namespace, both the F# v2.0 (regex) and the ISO/IEC 19757-7:2020 (code-point literal) `\u003cchar\u003e` content dialects are supported. | If a script uses ICU-only regex features, rewrite or use code-point literal `U+XXXX[-U+YYYY]`. |\n| Regex syntax is `java.util.regex.Pattern` (with `COMMENTS \\| UNICODE_CHARACTER_CLASS`).          | Stick to features common to ICU and Java regex.                  |\n| ICU-only regex features not supported: `\\N{...}` Unicode-name escapes, `[set1 \u0026\u0026 set2]` intersection inside char classes, ICU-specific properties. | Re-express with JDK-compatible regex.                            |\n| `CLDR` registry not implemented.                                                                 | Use ISO 10646 collections or `\u003cchar\u003e` regex instead.             |\n| IANA `miBenum` (numeric lookup) not implemented.                                                 | Use the IANA `name`.                                              |\n| `\u003crepertoire registry=\"IVD\"/\u003e` requires the `IVD_Sequences.txt` resource on the classpath. The bundled copy is from an older Unicode revision. | Replace `src/main/resources/external/crepdl/repo/IVD/IVD_Sequences.txt` with an up-to-date copy from \u003chttps://www.unicode.org/Public/UCD/latest/ucd/IVD_Sequences.txt\u003e and rebuild. |\n| `\u003cref href=\"...\"/\u003e` URIs are routed through an `ICREPDLRefResolver`. The default `DenyAllRefResolver` refuses every ref; scripts that use `\u003cref\u003e` must opt into a permissive resolver via the `CREPDLValidator.create(..., resolver)` overloads. See [\u0026sect;15](#15-security-considerations). | Pass `FileSystemRefResolver(root)` for trusted local-file refs, or `UnrestrictedRefResolver.INSTANCE` for the legacy \"fetch anything\" behaviour. |\n| CREPDL nesting / `\u003cref\u003e`-follow / ISO-10646-collection expansion is bounded at 100 levels.       | Restructure scripts that exceed it; the limit is `MAX_NESTING_DEPTH` in `CREPDLReader` and `MAX_EXPANSION_DEPTH` in `RefAndRepertoireExpander`. |\n\n---\n\n## 15. Security considerations\n\n### 15.1 Threat model\n\n`jcrepdl` was written for **trusted CREPDL documents** \u0026mdash; scripts\nauthored in-house or vetted before processing. If you process CREPDL XML\nor candidate strings that originate from untrusted sources (HTTP request\nbodies, file uploads, third-party feeds), read the rest of this section\nbefore wiring it into a production path.\n\n### 15.2 What is hardened\n\nThe DOM parser in `CREPDLReader._createBuilder()` runs with:\n\n- `javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING = true`\n- `disallow-doctype-decl = true` \u0026mdash; no `\u003c!DOCTYPE\u003e` declarations allowed; XXE is blocked at the gate\n- `external-general-entities = false`\n- `external-parameter-entities = false`\n- `nonvalidating/load-external-dtd = false`\n- `XIncludeAware = false`\n- `setValidating(false)` \u0026mdash; no DTD/XSD is supplied by the project, so validating mode is intentionally off\n\nA SAX `ErrorHandler` routes warnings through `LOGGER.warn` and errors\n(recoverable + fatal) through `LOGGER.error`. Errors and fatal errors\nare rethrown, so XML well-formedness violations always surface as a\n`CREPDLParseException` rather than being silently swallowed.\n\nRecursion is bounded in two places to prevent `StackOverflowError` on\npathological input:\n\n- `CREPDLReader._parseElement` \u0026mdash; max 100 nested CREPDL elements\n  (`MAX_NESTING_DEPTH`).\n- `RefAndRepertoireExpander._expandRecursive` \u0026mdash; max 100 combined\n  structural children + `\u003cref\u003e`-follow + ISO-10646-collection expansions\n  (`MAX_EXPANSION_DEPTH`).\n\n`\u003cref\u003e` cycles and CREPDL-script-defined ISO 10646 collection cycles are\ndetected independently and raise `CREPDLParseException` (see\n[\u0026sect;16](#16-differences-from-the-f-reference)).\n\nEvery `\u003cref href=\"...\"\u003e` URI is routed through an\n`ICREPDLRefResolver` (in `com.helger.crepdl.validate`). Implementations\nshipped with the library:\n\n| Resolver                  | Policy                                                                                       |\n| ------------------------- | -------------------------------------------------------------------------------------------- |\n| `DenyAllRefResolver`      | Refuses every URI. **This is the default** for every `CREPDLValidator.create(...)` overload that does not take a resolver. |\n| `FileSystemRefResolver`   | Accepts only `file:` URIs whose absolute, normalised path lies under a caller-supplied root directory. |\n| `UnrestrictedRefResolver` | Dereferences any scheme the JVM's URL handlers understand (`file:`, `http:`, `https:`, `jar:`, ...). Preserves the pre-SPI behaviour; **unsafe for untrusted input**. |\n\nUse the `CREPDLValidator.create(..., ICREPDLRefResolver)` overloads to\nopt into a permissive resolver, e.g.:\n\n```java\nCREPDLValidator v = CREPDLValidator.create (\n    URI.create (\"file:/srv/crepdl/top.crepdl\"),\n    new FileSystemRefResolver (Path.of (\"/srv/crepdl\")));\n```\n\n`forbiddenapis` blocks `jdk-unsafe`, `jdk-internal`, `jdk-reflection`,\n`jdk-system-out`, and `jdk-deprecated` API surface at build time.\n\n### 15.3 Known open issues\n\nThese are not mitigated by the library; callers handling untrusted input\nmust address them out-of-band:\n\n1.  **ReDoS via `\u003cchar\u003e` regex.** `CharMatcher.compile` runs the\n    `\u003cchar\u003e` content through `java.util.regex.Pattern`, which has no\n    timeout. Adversarial regex sources (e.g. `(a+)+$`) combined with an\n    attacker-controlled candidate string can hang the JVM via\n    catastrophic backtracking. Bound input length and run matching on\n    an interruptible thread if either side is untrusted.\n\n2.  **In-memory DOM, no input-size cap.** The whole document is loaded\n    before structural checks. Pass `CREPDLReader` a pre-bounded\n    `InputStream` if document size matters.\n\n3.  **Exception messages disclose paths and URIs.** `CREPDLParseException`\n    embeds full file paths, URIs, attribute values, and the SAX\n    `systemId` (also logged by the error handler). Do not surface raw\n    exception messages to untrusted clients.\n\n4.  **`UnrestrictedRefResolver` re-introduces the SSRF surface** if\n    selected (no scheme allow-list, no host filter, no time-out, no\n    fetch-size cap). Use it only when every CREPDL document fed to the\n    validator originates from a trusted source.\n\n---\n\n## 16. Differences from the F# reference\n\nThe Java port is a faithful translation with three intentional changes:\n\n1.  **Cycle detection on ISO 10646 CREPDL-script collections.**\n    Collection 283 (\"MODERN EUROPEAN SCRIPTS\") is defined recursively in\n    the F# data and includes `\u003crepertoire number=\"283\"/\u003e` inside its own\n    union body.  The F# reference tracks `\u003cref\u003e` cycles\n    but *not* CREPDL-script-collection cycles, so it would stack-overflow\n    on a script that uses collection 283. This Java port also tracks\n    ISO 10646 collection expansion chains; re-entry to the same collection\n    yields the algebraic union identity (empty union \u0026equiv; `FALSE` for\n    every input), which is correct because `union(X, ..., X) = union(X, ...)`.\n\n2.  **Regex engine is `java.util.regex.Pattern`** with the `COMMENTS` and\n    `UNICODE_CHARACTER_CLASS` flags, instead of ICU regex. This means\n    `\\p{Lu}`, `\\p{Mn}`, etc. work but `\\N{...}` Unicode-name escapes and\n    ICU-specific set operations inside character classes do not.\n\n3.  **Grapheme cluster iteration uses `java.text.BreakIterator`**\n    instead of ICU's `BreakIterator`. Both implement UAX #29 but the\n    JDK's evolution lags ICU; very recent emoji ZWJ sequences may break\n    differently.\n\n4.  **Code-point-literal `\u003cchar\u003e` content is accepted in addition to\n    regex.**  The F# reference treats `\u003cchar\u003e` content as ICU regex\n    exclusively. This port additionally accepts the ISO/IEC 19757-7:2020\n    code-point-literal dialect (`U+XXXX`, `U+XXXX-U+YYYY`, whitespace-\n    separated unions) inside `\u003cchar\u003e`, `\u003ckernel\u003e`, and `\u003chull\u003e`. The\n    detection is unambiguous and the regex path is preserved unchanged\n    for any content that is not pure code-point-literal syntax. See\n    [\u0026sect;2.5](#25-char-content-dialects-regex-vs-code-point-literal).\n    The implementation has been cross-validated against the SignalArc\n    CREPDL\u0026middot;CHECK service\n    (`apps.signalarc.com/crepdl-validator`); the bundled\n    `CodePointLiteralCharTest` runs SignalArc's own sample schema\n    through our validator and asserts the same two violating code points\n    that SignalArc reports.\n\nOtherwise the algorithm, the public method names, and the three-valued\nsemantics are 1:1.\n\n---\n\n## 17. License\n\njcrepdl is released under the **Apache License 2.0**.  See `LICENSE` (or\nthe headers in every source file) for the full text.\n\nThe original F# implementation by MURATA Makoto / CITPC is licensed under\nthe **MIT License** (https://github.com/CITPCSHARE/CREPDL/blob/Version2/LICENSE).\n\nThe bundled corpus under `src/test/resources/CREPDLScripts/` is licensed\nunder the **MIT License** by its upstream author.\n\nBundled data files derived from\n[`IVD_Sequences.txt`](https://www.unicode.org/Public/UCD/latest/ucd/IVD_Sequences.txt)\nand ISO/IEC 10646 collection definitions are subject to their respective\nupstream terms (Unicode Consortium License and ISO/IEC respectively); they\nare redistributed here in the form they were shipped in the F# project.\n\n---\n\n## 18. News and noteworthy\n\nv1.0.0 - 2026-06-12\n* Initial public release. Java 17 port of the F# CREPDL reference implementation (`CITPCSHARE/CREPDL`, branch `Version2`).\n\n**Core**\n\n- Full CREPDL v2.0 vocabulary: `\u003cunion\u003e`, `\u003cintersection\u003e`, `\u003cdifference\u003e`,\n  `\u003cref\u003e`, `\u003crepertoire\u003e`, `\u003cchar\u003e` (plus `\u003ckernel\u003e` / `\u003chull\u003e`).\n- Three-valued result (`TRUE` / `FALSE` / `UNKNOWN`) surfaced as\n  `ECREPDLValidationResult`; per-character and per-string/stream APIs on\n  `CREPDLValidator`.\n- Two evaluation modes: `EMode.CHARACTER` (Unicode code points, BMP +\n  surrogate pairs) and `EMode.GRAPHEME_CLUSTER` (UAX #29 via\n  `java.text.BreakIterator`).\n- Sealed AST hierarchy (`ICREPDLNode` + six records) and sealed registry\n  hierarchy (`IRegistry` + four records).\n\n**Registries**\n\n- `registry=\"10646\"` \u0026mdash; 323 inline + 44 out-of-line + 35\n  CREPDL-script-defined ISO/IEC 10646 collections bundled, including\n  Unicode age collections up to and including 17.0.\n- `registry=\"IANA\"` \u0026mdash; round-trip via `java.nio.charset.Charset`\n  (`name` only; `miBenum` not supported, matching the F# reference).\n- `registry=\"IVD\"` \u0026mdash; Adobe-Japan1 et al. validated against the\n  bundled `IVD_Sequences.txt`.\n- `registry=\"CLDR\"` \u0026mdash; recognised at parse time but intentionally\n  rejected with `CREPDLParseException(\"CLDR is not supported yet\")`,\n  matching the F# reference.\n\n**`\u003cchar\u003e` content dialects**\n\n- F# v2.0 ICU-flavoured regex (run through `java.util.regex.Pattern` with\n  `COMMENTS | UNICODE_CHARACTER_CLASS`).\n- ISO/IEC 19757-7:2020 code-point-literal syntax (`U+XXXX`,\n  `U+XXXX-U+YYYY`, whitespace-separated unions); detected by\n  `CodePointSyntax.toRegexOrNull` and translated transparently.\n- Cross-validated against the SignalArc CREPDL\u0026middot;CHECK service.\n\n**Reference resolution and recursion bounds**\n\n- `ICREPDLRefResolver` SPI for `\u003cref href=\"...\"\u003e` resolution, with three\n  bundled implementations: `DenyAllRefResolver` (default, refuses every\n  URI), `FileSystemRefResolver` (sandbox under a caller-supplied root,\n  rejects traversal escapes and non-`file:` schemes), and\n  `UnrestrictedRefResolver` (legacy \"fetch anything\" behaviour).\n- `\u003cref\u003e` cycles and ISO 10646 CREPDL-script-collection cycles tracked\n  independently; the F# reference only tracks the former and would\n  stack-overflow on collection 283 (MODERN EUROPEAN SCRIPTS).\n- Recursion depth bounded at 100 levels in two places\n  (`MAX_NESTING_DEPTH` in `CREPDLReader`, `MAX_EXPANSION_DEPTH` in\n  `RefAndRepertoireExpander`) to prevent `StackOverflowError` on\n  pathological input.\n\n**Hardened XML parsing**\n\n- DOM parser configured with `FEATURE_SECURE_PROCESSING`,\n  `disallow-doctype-decl`, external general and parameter entities off,\n  external DTD loading off, `XIncludeAware=false`. XXE is blocked at the\n  gate.\n- SAX `ErrorHandler` routes warnings through `LOGGER.warn` and errors /\n  fatal errors through `LOGGER.error` and rethrows them as\n  `CREPDLParseException`.\n\n**Build and tooling**\n\n- Maven build via `com.helger:parent-pom:3.0.5`; CI matrix on JDK 17, 21,\n  and 25; snapshot deploy on JDK 17.\n- `forbiddenapis` enforced (`jdk-unsafe`, `jdk-deprecated`,\n  `jdk-internal`, `jdk-non-portable`, `jdk-system-out`,\n  `jdk-reflection`); only `CREPDLBenchmark*` is excluded.\n- Production classpath logs through `slf4j-api` only; no SLF4J binding is\n  pulled in transitively.\n- Runnable `CREPDLBenchmark` main with three sections (lookups,\n  verifications, and coverage assessment for an arbitrary text on three\n  scales: block/plane, Unicode age, IANA charset).\n- 152-test suite across seven classes, including a 109-entry\n  parameterised corpus walk over the upstream `CREPDLScripts`\n  repository.\n\n---\n\nMy personal [Coding Styleguide](https://github.com/phax/meta/blob/master/CodingStyleguide.md) |\nIt is appreciated if you star the GitHub project if you like it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fjcrepdl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphax%2Fjcrepdl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fjcrepdl/lists"}