├── docs ├── pages │ ├── .gitignore │ ├── technical │ │ ├── _meta.json │ │ └── syntax.mdx │ ├── technical.mdx │ ├── _meta.json │ └── cli.mdx ├── base.next.config.js ├── .gitignore ├── core-typedoc.json ├── next.config.js ├── package.json ├── theme.config.tsx └── tsconfig.json ├── app ├── src │ ├── globals │ │ ├── index.ts │ │ ├── coreQuery.ts │ │ └── register.ts │ ├── index.scss │ ├── components │ │ ├── LawView │ │ │ ├── common.tsx │ │ │ ├── controls │ │ │ │ ├── ControlGlobalStyle.tsx │ │ │ │ ├── Declaration.tsx │ │ │ │ ├── LawNum.tsx │ │ │ │ ├── WrapHTMLControlRun.tsx │ │ │ │ └── ElawsPartialLawView.tsx │ │ │ ├── ErrorCatcher.tsx │ │ │ ├── ReplaceHTMLFigRun.tsx │ │ │ └── useAfterMountTask.tsx │ │ ├── useObserved.tsx │ │ └── LawtextAppPageState.ts │ ├── actions │ │ ├── showErrorModal.ts │ │ ├── scroll.ts │ │ ├── openFile.ts │ │ ├── temp_law.ts │ │ └── getOnMessage.ts │ ├── lawdata │ │ ├── loaders.ts │ │ ├── saveListJson.ts │ │ ├── common.ts │ │ └── searchLawID.ts │ ├── index.tsx │ ├── index.ejs │ └── law_util.ts ├── webpack-configs │ ├── QueryDocsPlugin.ts │ ├── WatchMessagePlugin.ts │ ├── getLawList.js │ └── CreateAppZipPlugin.ts ├── .gitignore ├── tsconfig.json └── playwright.config.ts ├── core ├── bin │ ├── build │ │ ├── package.json │ │ ├── types │ │ │ ├── defaultBasePath.d.ts │ │ │ ├── lawList.d.ts │ │ │ └── index.d.ts │ │ ├── defaultBasePath.js │ │ ├── tsconfig.json │ │ ├── index.js │ │ └── lawList.js │ └── saveLawdata.ts ├── src │ ├── law │ │ ├── std │ │ │ └── index.ts │ │ └── getLawList.js │ ├── path │ │ ├── .eslintrc.json │ │ └── v1 │ │ │ └── common.ts │ ├── util │ │ ├── node-fetch │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── zip.ts │ │ └── index.ts │ ├── renderer │ │ ├── index.ts │ │ ├── common │ │ │ ├── docx │ │ │ │ ├── EmptyParagraph.tsx │ │ │ │ ├── getPdfjs.js │ │ │ │ └── TextBoxRun.tsx │ │ │ ├── html.tsx │ │ │ └── index.tsx │ │ ├── xml.ts │ │ ├── docx.tsx │ │ ├── lawtext.ts │ │ ├── rules │ │ │ ├── noteLike.tsx │ │ │ ├── supplNote.tsx │ │ │ ├── htmlCSS.tsx │ │ │ ├── list.tsx │ │ │ ├── supplNote.spec.tsx │ │ │ ├── amendProvision.tsx │ │ │ ├── quoteStructRun.tsx │ │ │ └── arithFormulaRun.tsx │ │ └── html.tsx │ ├── parser │ │ ├── std │ │ │ ├── toCSTSettings.ts │ │ │ ├── factory.ts │ │ │ ├── env.ts │ │ │ └── rules │ │ │ │ ├── $arithFormula.spec.ts │ │ │ │ ├── columnsOrSentences.ts │ │ │ │ └── $supplNote.ts │ │ ├── cst │ │ │ ├── factory.ts │ │ │ ├── rules │ │ │ │ ├── $indents.ts │ │ │ │ ├── $blankLine.ts │ │ │ │ ├── $articleTitle.ts │ │ │ │ ├── lexical.ts │ │ │ │ ├── $squareAttr.ts │ │ │ │ ├── $lines.ts │ │ │ │ ├── $articleGroupNum.spec.ts │ │ │ │ ├── $articleGroupNum.ts │ │ │ │ ├── $indents.spec.ts │ │ │ │ ├── $paragraphItemTitle.ts │ │ │ │ ├── $blankLine.spec.ts │ │ │ │ ├── $articleLine.ts │ │ │ │ └── $articleTitle.spec.ts │ │ │ ├── env.ts │ │ │ ├── error.ts │ │ │ └── parse.ts │ │ └── lawtext.ts │ ├── analyzer │ │ ├── sentenceChildrenParser │ │ │ ├── factory.ts │ │ │ ├── util.ts │ │ │ ├── env.ts │ │ │ └── rules │ │ │ │ └── $nameInline.ts │ │ └── common │ │ │ └── index.ts │ ├── data │ │ ├── paths.ts │ │ └── loaders │ │ │ └── FetchElawsLoader.ts │ ├── node │ │ ├── el │ │ │ ├── jsonEL.ts │ │ │ ├── controls │ │ │ │ ├── text.ts │ │ │ │ ├── lawNum.ts │ │ │ │ ├── lawRef.ts │ │ │ │ ├── varRef.ts │ │ │ │ └── index.ts │ │ │ ├── elToXML.ts │ │ │ ├── loadEL.ts │ │ │ └── xmlToEL.ts │ │ └── cst │ │ │ └── inline.ts │ └── static │ │ └── law.css ├── webpack-configs │ ├── bundles.js │ ├── bundle-node.js │ └── bundle-browser.js ├── .mocharc.json ├── test │ ├── ensureTempTestDir.ts │ ├── assertLoader.spec.ts │ └── prepareTest.ts ├── .npmignore ├── .gitignore ├── LICENSE ├── tsconfig.json └── package.json ├── coverage ├── dockerfiles │ └── db │ │ └── Dockerfile ├── src │ ├── client │ │ ├── index.scss │ │ ├── index.html │ │ ├── index.tsx │ │ └── components │ │ │ └── LawtextDashboardPage.tsx │ ├── node-fetch │ │ ├── index.d.ts │ │ └── index.js │ ├── update │ │ ├── args.ts │ │ ├── worker.ts │ │ └── update.ts │ ├── config.ts │ ├── connection.ts │ └── server │ │ └── lawCoverages.ts ├── nodemon.json ├── bin │ ├── server.ts │ └── update.ts ├── .gitignore ├── docker-compose.yml ├── webpack-configs │ └── WatchMessagePlugin.ts ├── tsconfig.json └── package.json ├── .gitignore ├── LICENSE └── .github └── workflows └── docs.yml /docs/pages/.gitignore: -------------------------------------------------------------------------------- 1 | /lib -------------------------------------------------------------------------------- /docs/base.next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | }; 3 | -------------------------------------------------------------------------------- /app/src/globals/index.ts: -------------------------------------------------------------------------------- 1 | export * as lawtext from "./lawtext"; 2 | -------------------------------------------------------------------------------- /core/bin/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": "./types/index.d.ts" 3 | } -------------------------------------------------------------------------------- /app/src/globals/coreQuery.ts: -------------------------------------------------------------------------------- 1 | export * from "lawtext/dist/src/data/query"; 2 | -------------------------------------------------------------------------------- /coverage/dockerfiles/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mongo:7 2 | ENV LANG ja_JP.utf8 3 | -------------------------------------------------------------------------------- /coverage/src/client/index.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/scss/bootstrap.scss"; 2 | -------------------------------------------------------------------------------- /core/bin/build/types/defaultBasePath.d.ts: -------------------------------------------------------------------------------- 1 | export const defaultBasePath: string; 2 | -------------------------------------------------------------------------------- /core/src/law/std/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./stdEL"; 2 | export * from "./helpers"; 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /node_modules 3 | /next-env.d.ts 4 | /.next 5 | /.vscode 6 | ~$* 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /core/src/path/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "comma-dangle": ["error", "only-multiline"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /core/src/util/node-fetch/index.d.ts: -------------------------------------------------------------------------------- 1 | export const fetch: (url: RequestInfo, init?: RequestInit | undefined) => Promise; 2 | -------------------------------------------------------------------------------- /core/webpack-configs/bundles.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require("./bundle-node"), 3 | require("./bundle-browser"), 4 | ]; 5 | -------------------------------------------------------------------------------- /core/src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./lawtext"; 2 | export * from "./html"; 3 | export * from "./docx"; 4 | export * from "./xml"; 5 | -------------------------------------------------------------------------------- /core/bin/build/types/lawList.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} basePath 3 | */ 4 | export function buildLawList(basePath?: string): Promise; 5 | -------------------------------------------------------------------------------- /core/src/parser/std/toCSTSettings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | INDENT: " " as const, 3 | MARGIN: " " as const, 4 | EOL: "\r\n" as const, 5 | }; 6 | -------------------------------------------------------------------------------- /coverage/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRoot": [], 3 | "watch": [ 4 | "./dist/", 5 | "./node_modules/lawtext/" 6 | ], 7 | "ext": "js" 8 | } -------------------------------------------------------------------------------- /docs/pages/technical/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intermediate-data": {}, 3 | "parsing": {}, 4 | "syntax": {}, 5 | "analyzing": {}, 6 | "rendering": {} 7 | } -------------------------------------------------------------------------------- /docs/pages/technical.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Technical details 3 | --- 4 | 5 | # Technical details of Lawtext 6 | 7 | This section explains the technical details of Lawtext. 8 | -------------------------------------------------------------------------------- /core/bin/build/defaultBasePath.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const defaultBasePath = path.dirname(path.dirname(__dirname)); 4 | 5 | module.exports = { 6 | defaultBasePath, 7 | }; 8 | -------------------------------------------------------------------------------- /coverage/src/node-fetch/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {[url: RequestInfo, init?: RequestInit | undefined]} args 3 | */ 4 | export const fetch: (url: RequestInfo, init?: RequestInit | undefined) => Promise; 5 | -------------------------------------------------------------------------------- /core/src/parser/cst/factory.ts: -------------------------------------------------------------------------------- 1 | import { RuleFactory } from "generic-parser/lib/rules/factory"; 2 | import type { Env } from "./env"; 3 | 4 | export const factory = new RuleFactory(); 5 | 6 | export default factory; 7 | -------------------------------------------------------------------------------- /core/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension": ["js"], 3 | "exit": true, 4 | "timeout": 20000, 5 | "enable-source-maps": true, 6 | "spec": [ 7 | "dist/src/**/*.spec.js", 8 | "dist/test/**/*.spec.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /coverage/bin/server.ts: -------------------------------------------------------------------------------- 1 | import server from "../src/server"; 2 | 3 | if (typeof require !== "undefined" && require.main === module) { 4 | process.on("unhandledRejection", (listener) => { 5 | throw listener; 6 | }); 7 | server(); 8 | } 9 | -------------------------------------------------------------------------------- /core/bin/build/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { buildLawList } from "./lawList"; 2 | import { defaultBasePath } from "./defaultBasePath"; 3 | /** 4 | * @param {string} basePath 5 | */ 6 | export function build(basePath?: string): Promise; 7 | export { buildLawList, defaultBasePath }; 8 | -------------------------------------------------------------------------------- /coverage/src/update/args.ts: -------------------------------------------------------------------------------- 1 | export interface UpdateArgs { 2 | force: boolean, 3 | retry: boolean, 4 | dryRun: boolean, 5 | noParallel: boolean, 6 | maxDiffLength: number, 7 | before?: Date, 8 | lawID?: string, 9 | notificationEndpoint?: string, 10 | } 11 | -------------------------------------------------------------------------------- /core/src/analyzer/sentenceChildrenParser/factory.ts: -------------------------------------------------------------------------------- 1 | import { RuleFactory } from "generic-parser/lib/rules/factory"; 2 | import type { SentenceChildEL } from "../../node/cst/inline"; 3 | import type { Env } from "./env"; 4 | 5 | export const factory = new RuleFactory(); 6 | 7 | export default factory; 8 | -------------------------------------------------------------------------------- /coverage/src/node-fetch/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {[url: RequestInfo, init?: RequestInit | undefined]} args 3 | */ 4 | const fetch = (...args) => 5 | eval("import(\"node-fetch\")") 6 | .then( 7 | ({ default: fetch }) => 8 | fetch(...args), 9 | ); 10 | exports.fetch = fetch; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.cache 4 | *.DS_Store 5 | venv* 6 | *.pyc 7 | *.egg-info* 8 | __pycache__* 9 | __javascript__* 10 | .vscode 11 | staticfiles 12 | .env 13 | db.sqlite3 14 | debug.log 15 | out* 16 | node_modules 17 | package-lock.json 18 | cache.sqlite 19 | MANIFEST 20 | dist 21 | dist-dev 22 | dist-prod 23 | __tmp__* 24 | -------------------------------------------------------------------------------- /core/src/data/paths.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export const getLawdataPath = (dataDir: string): string => path.join(dataDir, "lawdata"); 4 | export const getListJsonPath = (dataDir: string): string => path.join(dataDir, "list.json"); 5 | export const getListCSVPath = (dataDir: string): string => path.join(dataDir, "lawdata", "all_law_list.csv"); 6 | -------------------------------------------------------------------------------- /core/bin/build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./*.js"], 3 | "compilerOptions": { 4 | "types": [], 5 | "target": "es2017", 6 | "module": "commonjs", 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "declaration": true, 10 | "emitDeclarationOnly": true, 11 | "strict": true, 12 | "outDir": "types", 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/parser/std/factory.ts: -------------------------------------------------------------------------------- 1 | import { RuleFactory } from "generic-parser/lib/rules/factory"; 2 | import type { Env } from "./env"; 3 | import type { VirtualLine } from "./virtualLine"; 4 | 5 | export const factory = new RuleFactory(); 6 | export type VirtualLineRuleFactory = RuleFactory; 7 | 8 | export default factory; 9 | -------------------------------------------------------------------------------- /core/test/ensureTempTestDir.ts: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | export const ensureTempTestDir = () => { 6 | const tempDir = path.join(os.tmpdir(), "lawtext_core_test"); 7 | if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }); 8 | return tempDir; 9 | }; 10 | 11 | export default ensureTempTestDir; 12 | -------------------------------------------------------------------------------- /core/src/renderer/common/docx/EmptyParagraph.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { w } from "./tags"; 3 | 4 | export const EmptyParagraph: React.FC = () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default EmptyParagraph; 15 | -------------------------------------------------------------------------------- /core/.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.cache 4 | *.DS_Store 5 | *.log 6 | venv* 7 | *.pyc 8 | *.egg-info* 9 | __pycache__* 10 | __javascript__* 11 | .vscode 12 | staticfiles 13 | .env 14 | db.sqlite3 15 | debug.log 16 | out* 17 | node_modules 18 | package-lock.json 19 | tsconfig.tsbuildinfo 20 | cache.sqlite 21 | MANIFEST 22 | /dist-dev 23 | /dist-bundle-dev 24 | /dist-bundle-prod 25 | /test/lawdata 26 | /data 27 | /mochawesome-report 28 | -------------------------------------------------------------------------------- /app/src/globals/register.ts: -------------------------------------------------------------------------------- 1 | import * as globals from "."; 2 | for (const key in globals) { 3 | if ("window" in global) { 4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 5 | // @ts-ignore 6 | window[key] = globals[key]; 7 | } else { 8 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 9 | // @ts-ignore 10 | global[key] = globals[key]; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/index.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/scss/bootstrap.scss"; 2 | 3 | .law-anchor-scroll-box { 4 | scroll-behavior: smooth; 5 | } 6 | 7 | .blink-once { 8 | animation: fadeBackground 3s; 9 | animation-fill-mode: forwards; 10 | } 11 | 12 | @keyframes fadeBackground { 13 | 0% { background-color: auto; } 14 | 25% { background-color: #fffadd; } 15 | 75% { background-color: #fffadd; } 16 | 100% { background-color: auto; } 17 | } -------------------------------------------------------------------------------- /core/src/node/el/jsonEL.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JsonEL: a tree structure that represents simplified structure of XML. A JsonEL object corresponds to an XML element. 3 | */ 4 | export interface JsonEL { 5 | /** The tag name of the element */ 6 | tag: string; 7 | 8 | /** The attributes of the element */ 9 | attr: Record; 10 | 11 | /** The children of the element */ 12 | children: (JsonEL | string)[]; 13 | } 14 | -------------------------------------------------------------------------------- /coverage/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.cache 4 | *.DS_Store 5 | venv* 6 | *.pyc 7 | *.egg-info* 8 | __pycache__* 9 | __javascript__* 10 | .vscode 11 | staticfiles 12 | .env 13 | db.sqlite3 14 | debug.log 15 | out* 16 | node_modules 17 | package-lock.json 18 | tsconfig.tsbuildinfo 19 | cache.sqlite 20 | MANIFEST 21 | /dist 22 | /dist-dev 23 | /dist-prod 24 | /dist-test 25 | /dist-bin 26 | /test/lawdata 27 | mochawesome-report 28 | /src/query-docs/temp-includes 29 | -------------------------------------------------------------------------------- /core/src/analyzer/sentenceChildrenParser/util.ts: -------------------------------------------------------------------------------- 1 | import type { Rule, Empty } from "generic-parser/lib/core"; 2 | import type { SentenceChildEL } from "../../node/cst/inline"; 3 | import type { ErrorMessage } from "../../parser/cst/error"; 4 | import type { Env } from "./env"; 5 | 6 | export type ValueRule = Rule 7 | export type WithErrorRule = Rule 8 | -------------------------------------------------------------------------------- /app/src/components/LawView/common.tsx: -------------------------------------------------------------------------------- 1 | import type { LawData } from "../../lawdata/common"; 2 | 3 | export const em = (input: number) => { 4 | const emSize = parseFloat(getComputedStyle(document.body).getPropertyValue("font-size")); 5 | return (emSize * input); 6 | }; 7 | 8 | export interface LawViewOptions { 9 | onError: (error: Error) => void; 10 | lawData: LawData, 11 | addAfterMountTask: (func: () => unknown) => void, 12 | firstPart: string, 13 | } 14 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.log 4 | *.cache 5 | *.DS_Store 6 | venv* 7 | *.pyc 8 | *.egg-info* 9 | __pycache__* 10 | __javascript__* 11 | .vscode 12 | staticfiles 13 | .env 14 | db.sqlite3 15 | debug.log 16 | out* 17 | node_modules 18 | package-lock.json 19 | tsconfig.tsbuildinfo 20 | cache.sqlite 21 | MANIFEST 22 | /dist 23 | /dist-dev 24 | /dist-prod 25 | /dist-bundle-dev 26 | /dist-bundle-prod 27 | /src/law/lawList.* 28 | /test/lawdata 29 | /data 30 | /mochawesome-report 31 | -------------------------------------------------------------------------------- /core/src/node/el/controls/text.ts: -------------------------------------------------------------------------------- 1 | import { EL } from ".."; 2 | 3 | export class __Text extends EL { 4 | public override tag = "__Text" as const; 5 | public override get isControl(): true { return true; } 6 | public override children: [string]; 7 | 8 | constructor( 9 | text: string, 10 | range: [start: number, end: number] | null = null, 11 | ) { 12 | super("__Text", {}, [], range); 13 | this.children = [text]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/node/el/controls/lawNum.ts: -------------------------------------------------------------------------------- 1 | import { EL } from ".."; 2 | 3 | export class ____LawNum extends EL { 4 | public override tag = "____LawNum" as const; 5 | public override get isControl(): true { return true; } 6 | public override children: [string]; 7 | 8 | constructor( 9 | text: string, 10 | range: [start: number, end: number] | null = null, 11 | ) { 12 | super("____LawNum", {}, [], range); 13 | this.children = [text]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/webpack-configs/QueryDocsPlugin.ts: -------------------------------------------------------------------------------- 1 | import type webpack from "webpack"; 2 | import path from "path"; 3 | import { generateDocs } from "../src/query-docs"; 4 | 5 | 6 | export default class QueryDocsPlugin { 7 | public apply(compiler: webpack.Compiler): void { 8 | compiler.hooks.afterEmit.tapPromise("CreateAppZipPlugin", async () => { 9 | const targetDir = path.join(compiler.outputPath, "query-docs"); 10 | await generateDocs(targetDir); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /coverage/src/config.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | dotenv.config(); 3 | 4 | const DATA_PATH = process.env["DATA_PATH"]; 5 | if (!DATA_PATH) throw new Error("Environment variable DATA_PATH not set"); 6 | 7 | const MONGODB_URI = process.env["MONGODB_URI"]; 8 | if (!MONGODB_URI) throw new Error("Environment variable MONGODB_URI not set"); 9 | 10 | const PORT = process.env["PORT"] ? Number(process.env["PORT"]) : 3000; 11 | 12 | export default { 13 | DATA_PATH, 14 | MONGODB_URI, 15 | PORT, 16 | }; 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.cache 4 | *.DS_Store 5 | venv* 6 | *.pyc 7 | *.egg-info* 8 | __pycache__* 9 | __javascript__* 10 | .vscode 11 | staticfiles 12 | .env 13 | db.sqlite3 14 | debug.log 15 | out* 16 | node_modules 17 | package-lock.json 18 | tsconfig.tsbuildinfo 19 | cache.sqlite 20 | MANIFEST 21 | /dist 22 | /dist-dev 23 | /dist-prod 24 | /dist-dev-local 25 | /dist-prod-local 26 | /dist-test 27 | /dist-bin 28 | /test/lawdata 29 | playwright-report 30 | playwright-results 31 | /src/query-docs/temp-includes 32 | -------------------------------------------------------------------------------- /docs/core-typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tsconfig": "../core/tsconfig.json", 3 | "entryPoints": ["../core/src/"], 4 | "basePath": "../", 5 | "exclude": ["../core/src/util/node-fetch/*", "**/*.spec.ts", "**/*.spec.tsx"], 6 | "entryPointStrategy": "expand", 7 | "useTsLinkResolution": true, 8 | "name": "Lawtext core references", 9 | "plugin": ["typedoc-plugin-markdown"], 10 | "cleanOutputDir": true, 11 | "readme": "none", 12 | "fileExtension": ".mdx", 13 | "membersWithOwnFile": ["Class", "Interface"], 14 | "out": "pages/lib" 15 | } 16 | -------------------------------------------------------------------------------- /core/src/renderer/xml.ts: -------------------------------------------------------------------------------- 1 | import type { EL } from "../node/el"; 2 | import type { JsonEL } from "../node/el/jsonEL"; 3 | import loadEL from "../node/el/loadEL"; 4 | import formatXML from "../util/formatXml"; 5 | 6 | export const renderXML = (elOrJsonEL: JsonEL | EL, withControlEl = false, format = false): string => { 7 | const el = loadEL(elOrJsonEL); 8 | let body = el.outerXML(withControlEl); 9 | if (format) body = formatXML(body); 10 | const xml = `\ 11 | 12 | ${body} 13 | `; 14 | return xml; 15 | }; 16 | 17 | export default renderXML; 18 | -------------------------------------------------------------------------------- /docs/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": {}, 3 | "background": {}, 4 | "technical": {}, 5 | "cli": {}, 6 | "lawtext-app": { 7 | "title": "Lawtext-app", 8 | "href": "https://yamachig.github.io/lawtext-app/", 9 | "newWindow": true, 10 | "type": "page" 11 | }, 12 | "github": { 13 | "title": "GitHub", 14 | "href": "https://github.com/yamachig/Lawtext", 15 | "newWindow": true, 16 | "type": "page" 17 | }, 18 | "---": { 19 | "type": "separator" 20 | }, 21 | "lib": { 22 | "title": "Library" 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/util/node-fetch/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable tsdoc/syntax */ 2 | /** 3 | * @param {[url: RequestInfo, init?: RequestInit | undefined]} args 4 | */ 5 | // eslint-disable-next-line no-redeclare 6 | const fetch = async (...args) => { 7 | try { 8 | const { default: fetch } = await eval("import(\"node-fetch\")"); 9 | return fetch(...args); 10 | 11 | } catch { 12 | // eslint-disable-next-line @typescript-eslint/no-var-requires 13 | const fetch = require("node-fetch").default; 14 | return fetch(...args); 15 | } 16 | }; 17 | exports.fetch = fetch; 18 | -------------------------------------------------------------------------------- /app/src/actions/showErrorModal.ts: -------------------------------------------------------------------------------- 1 | import { Modal } from "bootstrap"; 2 | 3 | export const ErrorModalID = "LawtextAppPage.ErrorModal"; 4 | export const showErrorModal = (title: string, bodyEl: string): void => { 5 | const modalEl = document.getElementById(ErrorModalID); 6 | const modalTitleEl = modalEl?.querySelector(".modal-title"); 7 | const modalBodyEl = modalEl?.querySelector(".modal-body"); 8 | if (!modalEl || !modalTitleEl || !modalBodyEl) return; 9 | const modal = new Modal(modalEl); 10 | modalTitleEl.innerHTML = title; 11 | modalBodyEl.innerHTML = bodyEl; 12 | modal.show(); 13 | }; 14 | -------------------------------------------------------------------------------- /core/test/assertLoader.spec.ts: -------------------------------------------------------------------------------- 1 | import { __loader } from "./prepareTest"; 2 | 3 | if (!__loader) { 4 | describe("Assert loader", () => { 5 | it("Assert loader", async function () { 6 | throw new Error(` 7 | Some tests were skipped and marked "pending" because the environment variable DATA_PATH was not set. 8 | You can specify the variable in the ".env" file at the top of the "core" directory. 9 | Please be aware that at the first test after setting DATA_PATH, law XML data will be downloaded and decompressed into the directory (the final size will be several GBs). 10 | `); 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next').default} 3 | **/ 4 | // eslint-disable-next-line @typescript-eslint/no-var-requires 5 | const nextra = require("nextra"); 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const baseConfig = require("./base.next.config"); 9 | 10 | 11 | const withNextra = nextra({ 12 | theme: "nextra-theme-docs", 13 | themeConfig: "./theme.config.tsx", 14 | }); 15 | 16 | const config = { 17 | ...baseConfig, 18 | output: "export", 19 | images: { 20 | unoptimized: true, 21 | }, 22 | trailingSlash: true, 23 | }; 24 | module.exports = withNextra(config); 25 | 26 | -------------------------------------------------------------------------------- /app/src/components/LawView/controls/ControlGlobalStyle.tsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | 3 | export const ControlGlobalStyle = createGlobalStyle` 4 | .control-parentheses-content[data-parentheses_type="square"] { 5 | color: rgb(158, 79, 0); 6 | } 7 | 8 | .lawtext-container-ref-open > .lawtext-container-ref-text { 9 | background-color: rgba(127, 127, 127, 0.15); 10 | border-bottom: 1px solid rgb(40, 167, 69); 11 | } 12 | 13 | .lawtext-container-ref-text:hover { 14 | background-color: rgb(255, 249, 160); 15 | border-bottom: 1px solid rgb(40, 167, 69); 16 | } 17 | `; 18 | 19 | export default ControlGlobalStyle; 20 | -------------------------------------------------------------------------------- /coverage/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | name: lawtext-coverage 4 | 5 | services: 6 | 7 | db: 8 | container_name: lawtext-coverage-db 9 | hostname: lawtext-coverage-db 10 | ports: 11 | - 27017:27017 12 | build: ./dockerfiles/db 13 | environment: 14 | MONGO_INITDB_ROOT_USERNAME: admin 15 | MONGO_INITDB_ROOT_PASSWORD: admin 16 | volumes: 17 | - db_data:/var/lib/mongo/data 18 | healthcheck: 19 | test: "mongosh --port 27017 --eval \"exit();\" || exit 1" 20 | interval: 2s 21 | timeout: 2s 22 | retries: 10 23 | start_period: 2s 24 | 25 | volumes: 26 | db_data: 27 | name: lawtext-coverage-db-volume 28 | -------------------------------------------------------------------------------- /core/bin/build/index.js: -------------------------------------------------------------------------------- 1 | const { buildLawList: _buildLawList } = require("./lawList.js"); 2 | const { defaultBasePath: _defaultBasePath } = require("./defaultBasePath.js"); 3 | 4 | const buildLawList = _buildLawList; 5 | const defaultBasePath = _defaultBasePath; 6 | 7 | /** 8 | * @param {string} basePath 9 | */ 10 | const build = async (basePath = defaultBasePath) => { 11 | 12 | // console.log("Compiling lawnum_table..."); 13 | await buildLawList(basePath); 14 | }; 15 | 16 | module.exports = { 17 | buildLawList, 18 | defaultBasePath, 19 | build, 20 | }; 21 | 22 | if (typeof require !== "undefined" && require.main === module) { 23 | build().catch(console.error); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /core/src/law/getLawList.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable tsdoc/syntax */ 2 | /** @type {{ lawID: string; lawNum: string; lawTitle: string; abbrev: string[] }[] | null} */ 3 | let _lawList = null; 4 | 5 | export const getLawList = async () => { 6 | if (!_lawList) { 7 | /** @type {[string, string, string, string[]][]} */ 8 | let lawList; 9 | try { 10 | lawList = (await eval("import(\"./lawList.json\", { assert: { type: \"json\" } })")).default; 11 | } catch { 12 | lawList = require("./lawList.json"); 13 | } 14 | _lawList = lawList.map(([lawID, lawNum, lawTitle, abbrev]) => ({ lawID, lawNum, lawTitle, abbrev })); 15 | } 16 | return _lawList; 17 | }; 18 | -------------------------------------------------------------------------------- /app/src/lawdata/loaders.ts: -------------------------------------------------------------------------------- 1 | 2 | import { FetchElawsLoader } from "lawtext/dist/src/data/loaders/FetchElawsLoader"; 3 | import { FetchStoredLoader } from "lawtext/dist/src/data/loaders/FetchStoredLoader"; 4 | 5 | const _dataPath = "./data"; 6 | export const elawsLoader = new FetchElawsLoader(); 7 | export const storedLoader = new FetchStoredLoader(_dataPath); 8 | 9 | export const ensureFetch = async (): Promise<{isFile: boolean, canFetch: boolean}> => { 10 | const isFile = location.protocol === "file:"; 11 | try { 12 | const res = await fetch("./index.html", { method: "HEAD" }); 13 | return { isFile, canFetch: res.ok }; 14 | } catch (e) { 15 | return { isFile, canFetch: false }; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/$indents.ts: -------------------------------------------------------------------------------- 1 | import factory from "../factory"; 2 | import type { WithErrorRule } from "../util"; 3 | 4 | export const $indents: WithErrorRule<{indentTexts: string[], indentDepth: number}> = factory 5 | .withName("indent") 6 | .sequence(s => s 7 | // eslint-disable-next-line no-irregular-whitespace 8 | .and(r => r.zeroOrMore(r => r.regExp(/^(?: {2}| |\t)/)), "indentTexts") 9 | .action(({ indentTexts }) => { 10 | return { 11 | value: { 12 | indentTexts, 13 | indentDepth: indentTexts.length, 14 | }, 15 | errors: [], 16 | }; 17 | }) 18 | ) 19 | ; 20 | 21 | export default $indents; 22 | 23 | -------------------------------------------------------------------------------- /core/src/parser/lawtext.ts: -------------------------------------------------------------------------------- 1 | import { parse as cstParse } from "./cst/parse"; 2 | import { initialEnv } from "./std/env"; 3 | import $law from "./std/rules/$law"; 4 | import { toVirtualLines } from "./std/virtualLine"; 5 | 6 | export const parse = (lawtext: string) => { 7 | const lines = cstParse(lawtext); 8 | const vls = toVirtualLines(lines.value); 9 | const env = initialEnv({ target: lawtext }); 10 | const law = $law.match(0, vls, env); 11 | if (law.ok) { 12 | return { 13 | value: law.value.value, 14 | virtualLines: vls, 15 | errors: [...lines.errors, ...law.value.errors], 16 | }; 17 | } else { 18 | throw new Error(`parse failed: offset ${law.offset}; expected ${law.expected}`); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /app/webpack-configs/WatchMessagePlugin.ts: -------------------------------------------------------------------------------- 1 | import type webpack from "webpack"; 2 | 3 | export default class WatchMessagePlugin { 4 | public apply(compiler: webpack.Compiler): void { 5 | compiler.hooks.beforeRun.tap("WatchMessagePlugin", () => { 6 | console.log("\x1b[36m" + "Begin compile at " + new Date() + " \x1b[39m"); 7 | }); 8 | compiler.hooks.watchRun.tap("WatchMessagePlugin", () => { 9 | console.log("\x1b[36m" + "Begin compile at " + new Date() + " \x1b[39m"); 10 | }); 11 | compiler.hooks.done.tap("WatchMessagePlugin", () => { 12 | setTimeout(() => { 13 | console.log("\x1b[36m" + "Done compile at " + new Date() + " \x1b[39m"); 14 | }, 30); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coverage/webpack-configs/WatchMessagePlugin.ts: -------------------------------------------------------------------------------- 1 | import type webpack from "webpack"; 2 | 3 | export default class WatchMessagePlugin { 4 | public apply(compiler: webpack.Compiler): void { 5 | compiler.hooks.beforeRun.tap("WatchMessagePlugin", () => { 6 | console.log("\r\n\x1b[36m" + "Begin compile at " + new Date() + " \x1b[39m"); 7 | }); 8 | compiler.hooks.watchRun.tap("WatchMessagePlugin", () => { 9 | console.log("\r\n\x1b[36m" + "Begin compile at " + new Date() + " \x1b[39m"); 10 | }); 11 | compiler.hooks.done.tap("WatchMessagePlugin", () => { 12 | setTimeout(() => { 13 | console.log("\r\n\x1b[36m" + "Done compile at " + new Date() + " \x1b[39m"); 14 | }, 30); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coverage/src/connection.ts: -------------------------------------------------------------------------------- 1 | 2 | import mongoose from "mongoose"; 3 | import type { LawCoverage } from "./lawCoverage"; 4 | import { lawCoverageSchema } from "./schema"; 5 | 6 | export interface ConnectionInfo { 7 | connection: mongoose.Connection; 8 | lawCoverage: mongoose.Model; 9 | } 10 | 11 | export const connect = async (uri: string): Promise => { 12 | const connection = await mongoose.createConnection( 13 | uri, 14 | { 15 | // useNewUrlParser: true, 16 | // useUnifiedTopology: true, 17 | // useCreateIndex: true, 18 | }, 19 | ); 20 | const lawCoverage = connection.model("LawCoverage", lawCoverageSchema); 21 | return { 22 | connection, 23 | lawCoverage, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /app/webpack-configs/getLawList.js: -------------------------------------------------------------------------------- 1 | /** @type {{ lawID: string; lawNum: string; lawTitle: string; abbrev: string[] }[] | null} */ 2 | let _lawList = null; 3 | 4 | const getLawListPromise = (async () => { 5 | /** @type {[string, string, string, string[]][]} */ 6 | let lawList; 7 | try { 8 | lawList = (await eval("import(\"./lawList.json\", { assert: { type: \"json\" } })")).default; 9 | } catch { 10 | lawList = await (await fetch("https://lic857vlz1.execute-api.ap-northeast-1.amazonaws.com/prod/Lawtext-API?method=lawlist")).json(); 11 | } 12 | return lawList.map(([lawID, lawNum, lawTitle, abbrev]) => ({ lawID, lawNum, lawTitle, abbrev })); 13 | })(); 14 | 15 | export const getLawList = async () => { 16 | if (!_lawList) { 17 | _lawList = await getLawListPromise; 18 | } 19 | return _lawList; 20 | }; 21 | -------------------------------------------------------------------------------- /core/src/analyzer/common/index.ts: -------------------------------------------------------------------------------- 1 | import * as std from "../../law/std"; 2 | import type { EL } from "../../node/el"; 3 | 4 | // export const ignoreAnalysisTags = [ 5 | // "QuoteStruct", 6 | // "NewProvision", 7 | // // "LawNum", 8 | // // "LawTitle", 9 | // // "TOC", 10 | // // "ArticleTitle", 11 | // // ...std.paragraphItemTitleTags, 12 | // // "SupplProvision", 13 | // ] as const; 14 | 15 | // export type IgnoreAnalysis = ( 16 | // | std.QuoteStruct 17 | // | std.NewProvision 18 | // | std.SupplProvision 19 | // ); 20 | 21 | export const isIgnoreAnalysis = (el: EL | string) => { 22 | if (typeof el === "string") return false; 23 | else if (std.isQuoteStruct(el)) return true; 24 | else if (std.isNewProvision(el)) return true; 25 | else if (std.isSupplProvision(el) && el.attr.AmendLawNum) return true; 26 | else return false; 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/$blankLine.ts: -------------------------------------------------------------------------------- 1 | import factory from "../factory"; 2 | import { BlankLine } from "../../../node/cst/line"; 3 | import type { WithErrorRule } from "../util"; 4 | 5 | /** 6 | * The parser rule for {@link BlankLine} that represents a blank line. Please see the source code for the detailed syntax, and the [test code](https://github.com/yamachig/Lawtext/blob/main/core/src/parser/cst/rules/$blankLine.spec.ts) for examples. 7 | */ 8 | export const $blankLine: WithErrorRule = factory 9 | .withName("blankLine") 10 | .sequence(s => s 11 | // eslint-disable-next-line no-irregular-whitespace 12 | .and(r => r.regExp(/^[  \t]*\r?\n/), "text") 13 | .action(({ range, text }) => { 14 | return { value: new BlankLine({ range: range(), lineEndText: text }), errors: [] }; 15 | }) 16 | ) 17 | ; 18 | 19 | export default $blankLine; 20 | 21 | -------------------------------------------------------------------------------- /app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { HashRouter, Route, Routes } from "react-router-dom"; 4 | 5 | import "bootstrap"; 6 | import "./index.scss"; 7 | 8 | import { LawtextAppPage } from "./components/LawtextAppPage"; 9 | import { DownloadPage } from "./components/DownloadPage"; 10 | 11 | const App = () => { 12 | return ( 13 | 14 | 15 | } /> 16 | } /> 17 | } /> 18 | 19 | 20 | ); 21 | }; 22 | 23 | const rootElement = document.getElementById("root"); 24 | 25 | if (rootElement) { 26 | createRoot(rootElement).render(); 27 | } 28 | 29 | import "./globals/register"; 30 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/$articleTitle.ts: -------------------------------------------------------------------------------- 1 | import factory from "../factory"; 2 | import type { WithErrorRule } from "../util"; 3 | import { arabicDigits, kanjiDigits } from "./lexical"; 4 | 5 | 6 | export const $articleTitle: WithErrorRule = factory 7 | .withName("articleTitle") 8 | .sequence(c => c 9 | .and(r => r 10 | .choice(c => c 11 | .or(r => r 12 | .regExp(new RegExp(`^第[${kanjiDigits}]+[条條](?:[のノ][${kanjiDigits}]+)*`)) // e.g. "第十二条", "第一条の二", "第一条の二の三" 13 | ) 14 | .or(r => r 15 | .regExp(new RegExp(`^第[${arabicDigits}]+[条條](?:[のノ][${arabicDigits}]+)*`)) // e.g. "第12条", "第1条の2", "第1条の2の3" 16 | ) 17 | ) 18 | , "title") 19 | .action(({ title }) => { 20 | return { value: title, errors: [] }; 21 | }) 22 | ) 23 | ; 24 | 25 | export default $articleTitle; 26 | -------------------------------------------------------------------------------- /core/src/renderer/docx.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { EL } from "../node/el"; 3 | import * as std from "../law/std"; 4 | import { DOCXLaw } from "./rules/law"; 5 | import { renderDocxAsync as innerRenderDocxAsync } from "./common/docx/file"; 6 | import type { DOCXOptions } from "./common/docx/component"; 7 | import { DOCXAnyELs } from "./rules/any"; 8 | import loadEL from "../node/el/loadEL"; 9 | import type { JsonEL } from "../node/el/jsonEL"; 10 | 11 | 12 | export const renderDocxAsync = (elOrJsonEL: JsonEL | EL, docxOptions?: DOCXOptions): Promise => { 13 | const el = loadEL(elOrJsonEL); 14 | const element = std.isLaw(el) 15 | ? 16 | : ; 17 | 18 | return innerRenderDocxAsync(element, docxOptions); 19 | }; 20 | 21 | export default renderDocxAsync; 22 | -------------------------------------------------------------------------------- /app/src/components/LawView/controls/Declaration.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from "react"; 3 | import type { HTMLComponentProps } from "lawtext/dist/src/renderer/common/html"; 4 | import { HTMLSentenceChildrenRun } from "lawtext/dist/src/renderer/rules/sentenceChildrenRun"; 5 | import styled from "styled-components"; 6 | import type { SentenceChildEL } from "lawtext/dist/src/node/cst/inline"; 7 | import type { ____Declaration } from "lawtext/dist/src/node/el/controls/declaration"; 8 | 9 | 10 | const DeclarationSpan = styled.span` 11 | color: rgb(40, 167, 69); 12 | `; 13 | 14 | export interface ____DeclarationProps { el: ____Declaration } 15 | 16 | export const Declaration = (props: HTMLComponentProps & ____DeclarationProps) => { 17 | const { el, htmlOptions } = props; 18 | return ( 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Declaration; 26 | -------------------------------------------------------------------------------- /app/src/components/LawView/controls/LawNum.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type * as std from "lawtext/dist/src/law/std"; 3 | import type { HTMLComponentProps } from "lawtext/dist/src/renderer/common/html"; 4 | import styled from "styled-components"; 5 | import { HTMLSentenceChildrenRun } from "lawtext/dist/src/renderer/rules/sentenceChildrenRun"; 6 | import type { SentenceChildEL } from "lawtext/dist/src/node/cst/inline"; 7 | import { lawNumLikeToLawNum } from "lawtext/dist/src/law/lawNum"; 8 | 9 | 10 | const LawNumA = styled.a` 11 | `; 12 | 13 | export interface LawNumProps { el: std.__EL } 14 | 15 | export const LawNum = (props: HTMLComponentProps & LawNumProps) => { 16 | const { el, htmlOptions } = props; 17 | return ( 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default LawNum; 25 | 26 | 27 | -------------------------------------------------------------------------------- /core/src/renderer/lawtext.ts: -------------------------------------------------------------------------------- 1 | import * as std from "../law/std"; 2 | import { isControl, isStdEL } from "../law/std"; 3 | import type { EL } from "../node/el"; 4 | import { anyToLines } from "../parser/std/rules/$any"; 5 | import { lawToLines } from "../parser/std/rules/$law"; 6 | import { NotImplementedError } from "../util"; 7 | 8 | export const renderLawtext = (el: EL, indentTexts: string[] = []): string => { 9 | let ret = ""; 10 | if (std.isLaw(el)) { 11 | const lines = lawToLines(el, indentTexts); 12 | ret += lines.map(l => l.text()).join(""); 13 | } else if (isStdEL(el) || isControl(el)) { 14 | const lines = anyToLines(el, indentTexts); 15 | ret += lines.map(l => l.text()).join(""); 16 | } else { 17 | throw new NotImplementedError(`render ${el.tag}`); 18 | } 19 | ret = ret.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n").replace(/(\r?\n\r?\n)(?:\r?\n)+/g, "$1").replace(/(?:\r?\n)?$/, "\r\n").replace(/(?:\r?\n)+$/, "\r\n"); 20 | return ret; 21 | }; 22 | 23 | export default renderLawtext; 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 yamachi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/actions/scroll.ts: -------------------------------------------------------------------------------- 1 | const blinkOnceClassName = "blink-once"; 2 | 3 | export const scrollToLawAnchor = (id: string): void => { 4 | for (const el of Array.from(document.getElementsByClassName("law-anchor"))) { 5 | if ((el as HTMLElement).dataset.el_id === id) { 6 | const scrollEL = document.getElementsByClassName("law-anchor-scroll-box")[0]; 7 | if (!scrollEL) return; 8 | const elRect = el.getBoundingClientRect(); 9 | const scrollELRect = scrollEL.getBoundingClientRect(); 10 | scrollEL.scrollTop += elRect.top - scrollELRect.top - 60; 11 | 12 | if (el.nextSibling && el.nextSibling instanceof Element && !el.nextSibling.classList.contains(blinkOnceClassName)) { 13 | const blinkEl = el.nextSibling; 14 | blinkEl.classList.add(blinkOnceClassName); 15 | setTimeout(() => { 16 | blinkEl.classList.remove(blinkOnceClassName); 17 | }, 5000); 18 | } 19 | return; 20 | } 21 | } 22 | console.error(`scrollToLawAnchor(id=${id}) could not find id.`); 23 | }; 24 | -------------------------------------------------------------------------------- /core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 yamachi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/actions/openFile.ts: -------------------------------------------------------------------------------- 1 | const readFileAsText = (file: Blob): Promise => { 2 | const reader = new FileReader(); 3 | 4 | return new Promise((resolve, reject) => { 5 | reader.onerror = () => { 6 | reader.abort(); 7 | reject(reader.error); 8 | }; 9 | reader.onload = () => { 10 | resolve(reader.result as string); 11 | }; 12 | reader.readAsText(file); 13 | }); 14 | }; 15 | 16 | 17 | export const OpenFileInputName = "LawtextAppPage.OpenFileInput"; 18 | export const openFile = (): void => { 19 | const els = document.getElementsByName(OpenFileInputName); 20 | if (els) { 21 | els[0].click(); 22 | } 23 | }; 24 | 25 | export const readFileInput = async (): Promise => { 26 | const openFileInput = document.getElementsByName(OpenFileInputName).item(0) as HTMLInputElement; 27 | if (!openFileInput) return null; 28 | const file = openFileInput.files ? openFileInput.files[0] : null; 29 | if (!file) return null; 30 | const text = await readFileAsText(file); 31 | openFileInput.value = ""; 32 | return text; 33 | }; 34 | -------------------------------------------------------------------------------- /core/src/analyzer/sentenceChildrenParser/env.ts: -------------------------------------------------------------------------------- 1 | import type { MatchFail, MatchContext, BasePos, BaseEnv } from "generic-parser/lib/core"; 2 | import type { SentenceChildEL } from "../../node/cst/inline"; 3 | 4 | export interface Env extends BaseEnv { 5 | state: { 6 | maxOffsetMatchFail: MatchFail | null; 7 | maxOffsetMatchContext: MatchContext | null; 8 | }; 9 | } 10 | 11 | export interface InitialEnvOptions { 12 | target: string, 13 | options?: Record, 14 | baseOffset?: number, 15 | } 16 | 17 | export const initialEnv = (initialEnvOptions: InitialEnvOptions): Env => { 18 | const { options = {}, baseOffset = 0 } = initialEnvOptions; 19 | const registerCurrentRangeTarget = () => { /**/ }; 20 | const offsetToPos = (_: SentenceChildEL[], offset: number) => ({ offset }); 21 | 22 | const state = { 23 | maxOffsetMatchFail: null as null | MatchFail, 24 | maxOffsetMatchContext: null as null | MatchContext, 25 | }; 26 | 27 | return { 28 | options, 29 | registerCurrentRangeTarget, 30 | offsetToPos, 31 | state, 32 | baseOffset, 33 | }; 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /core/src/node/el/controls/lawRef.ts: -------------------------------------------------------------------------------- 1 | import { EL } from ".."; 2 | 3 | 4 | export interface LawRefOptions { 5 | includingDeclarationID?: string, 6 | suggestedLawTitle?: string, 7 | lawNum: string, 8 | range: [start: number, end: number] | null, 9 | } 10 | 11 | export class ____LawRef extends EL { 12 | public override tag = "____LawRef" as const; 13 | public override get isControl(): true { return true; } 14 | public override attr: { 15 | includingDeclarationID?: string, 16 | suggestedLawTitle?: string, 17 | lawNum: string, 18 | }; 19 | 20 | constructor(options: LawRefOptions) { 21 | super("____LawRef", {}, [], options.range); 22 | 23 | const { includingDeclarationID, suggestedLawTitle, lawNum } = options; 24 | 25 | this.attr = { 26 | ...( 27 | (includingDeclarationID !== undefined) 28 | ? { includingDeclarationID } 29 | : {} 30 | ), 31 | ...( 32 | (suggestedLawTitle !== undefined) 33 | ? { suggestedLawTitle } 34 | : {} 35 | ), 36 | lawNum, 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/actions/temp_law.ts: -------------------------------------------------------------------------------- 1 | import type { LawQueryItem } from "lawtext/dist/src/data/query"; 2 | 3 | export const storeTempLaw = (text: string): string => { 4 | const id = `temp_law_${Math.floor(Math.random() * 1000000000)}`; 5 | localStorage.setItem( 6 | "temp_law:" + id, 7 | JSON.stringify({ 8 | datetime: new Date().toISOString(), 9 | text, 10 | }), 11 | ); 12 | return id; 13 | }; 14 | 15 | export const showLaw = async (textOrLaw: string | LawQueryItem): Promise => { 16 | const text = typeof textOrLaw === "string" ? textOrLaw : await textOrLaw.getXML(); 17 | if (!text) { 18 | console.error("showLaw: XML cannot be fetched."); 19 | return; 20 | } 21 | const id = storeTempLaw(text); 22 | window.open(`${location.protocol}//${location.host}${location.pathname}#${id}`); 23 | }; 24 | 25 | export const getTempLaw = (id: string): string | null => { 26 | const m = /^temp_law_(\d+)$/.exec(id); 27 | if (!m) return null; 28 | const json = localStorage.getItem("temp_law:" + id); 29 | if (json === null) { 30 | throw new Error(`一時データが見つかりませんでした (id: ${id})`); 31 | } 32 | const { text } = JSON.parse(json); 33 | return text as string; 34 | }; 35 | -------------------------------------------------------------------------------- /core/src/renderer/rules/noteLike.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type * as std from "../../law/std"; 3 | import type { HTMLComponentProps } from "../common/html"; 4 | import { elProps, wrapHTMLComponent } from "../common/html"; 5 | import type { DOCXComponentProps } from "../common/docx/component"; 6 | import { wrapDOCXComponent } from "../common/docx/component"; 7 | import { DOCXAnyELs, HTMLAnyELs } from "./any"; 8 | 9 | 10 | export interface NoteLikeProps { 11 | el: std.NoteLike, 12 | indent: number, 13 | } 14 | 15 | export const HTMLNoteLikeCSS = /*css*/` 16 | 17 | `; 18 | 19 | export const HTMLNoteLike = wrapHTMLComponent("HTMLNoteLike", ((props: HTMLComponentProps & NoteLikeProps) => { 20 | 21 | const { el, htmlOptions, indent } = props; 22 | 23 | return ( 24 |
25 | 26 |
27 | ); 28 | })); 29 | 30 | export const DOCXNoteLike = wrapDOCXComponent("DOCXNoteLike", ((props: DOCXComponentProps & NoteLikeProps) => { 31 | 32 | const { el, docxOptions, indent } = props; 33 | 34 | return ; 35 | })); 36 | -------------------------------------------------------------------------------- /core/src/node/el/elToXML.ts: -------------------------------------------------------------------------------- 1 | import type { JsonEL } from "./jsonEL"; 2 | 3 | const xmlReplacers: Record = { 4 | "<": "<", 5 | ">": ">", 6 | "&": "&", 7 | "\"": """, 8 | "'": "'", 9 | }; 10 | 11 | export const wrapXML = (el: JsonEL, inner: string): string => { 12 | const attr = Object.keys(el.attr) 13 | .map(key => ` ${key}="${el.attr[key]?.replace(/[<>&"']/g, c => xmlReplacers[c]) ?? ""}"`) 14 | .join(""); 15 | if (inner) { 16 | return `<${el.tag}${attr}>${inner}`; 17 | } else { 18 | return `<${el.tag}${attr}/>`; 19 | } 20 | }; 21 | 22 | export const outerXML = (el: JsonEL, withControlEl = false): string => { 23 | const inner = innerXML(el, withControlEl); 24 | if (withControlEl || el.tag[0] !== "_") { 25 | return wrapXML(el, inner); 26 | } else { 27 | return inner; 28 | } 29 | }; 30 | 31 | export const innerXML = (el: JsonEL, withControlEl = false): string => { 32 | if (!el.children) console.error(el); 33 | return el.children.map(child => 34 | (typeof child === "string") 35 | ? child.replace(/[<>&"']/g, c => xmlReplacers[c]) 36 | : outerXML(child, withControlEl), 37 | ).join(""); 38 | }; 39 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npx concurrently -n core,docs \"npx typedoc --options core-typedoc.json --watch --preserveWatchOutput\" \"next dev\"", 4 | "build": "npm run core-docs && next build", 5 | "build-core-bundles": "cd \"../core\" && npm run build-bundles:prod -- --output-path=\"../docs/out/static/lawtext_bundles\"", 6 | "start": "next start", 7 | "lint": "next lint", 8 | "core-docs-watch": "npx typedoc --options core-typedoc.json --watch", 9 | "core-docs": "npx typedoc --options core-typedoc.json", 10 | "export": "npm run build && npm run build-core-bundles" 11 | }, 12 | "dependencies": { 13 | "next": "^14.2.4", 14 | "nextra": "^2.13.4", 15 | "nextra-theme-docs": "^2.13.4", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "typedoc": "^0.27.7", 19 | "typedoc-plugin-markdown": "^4.4.2" 20 | }, 21 | "devDependencies": { 22 | "@next/eslint-plugin-next": "^13.5.6", 23 | "@types/node": "^20.14.8", 24 | "@types/react": "^18.3.3", 25 | "concurrently": "^8.2.2", 26 | "eslint": "^8.57.0", 27 | "eslint-plugin-react": "^7.34.3", 28 | "eslint-plugin-react-hooks": "^4.6.2", 29 | "typescript": "^5.5.2", 30 | "typescript-eslint": "^7.13.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/node/el/loadEL.ts: -------------------------------------------------------------------------------- 1 | import { EL } from "."; 2 | import { controlFromEL } from "./controls"; 3 | import type { JsonEL } from "./jsonEL"; 4 | 5 | 6 | export const loadEL = (rawLaw: T): T extends string ? string : EL => { 7 | if (typeof rawLaw === "string") { 8 | return rawLaw as unknown as T extends string ? string : EL; 9 | } else { 10 | if (!rawLaw.children) { 11 | console.error("[load_el]", rawLaw); 12 | } 13 | const attr = { ...rawLaw.attr }; 14 | let id = undefined as number | undefined; 15 | let range = undefined as [number, number] | undefined; 16 | if ("__id" in rawLaw.attr) { 17 | id = JSON.parse(rawLaw.attr["__id"] ?? ""); 18 | delete attr["__id"]; 19 | } 20 | if ("__range" in rawLaw.attr) { 21 | range = JSON.parse(rawLaw.attr["__range"] ?? ""); 22 | delete attr["__range"]; 23 | } 24 | const _el = new EL( 25 | rawLaw.tag, 26 | attr, 27 | rawLaw.children.map(loadEL), 28 | range, 29 | id, 30 | ); 31 | const el = _el.isControl ? controlFromEL(_el) : _el; 32 | return el as unknown as T extends string ? string : EL; 33 | } 34 | }; 35 | 36 | export default loadEL; 37 | -------------------------------------------------------------------------------- /core/src/parser/std/env.ts: -------------------------------------------------------------------------------- 1 | import type { MatchFail, MatchContext, BasePos, BaseEnv } from "generic-parser/lib/core"; 2 | import type { VirtualLine } from "./virtualLine"; 3 | 4 | export interface Env extends BaseEnv { 5 | state: { 6 | maxOffsetMatchFail: MatchFail | null; 7 | maxOffsetMatchContext: MatchContext | null; 8 | }; 9 | } 10 | 11 | export interface InitialEnvOptions { 12 | target: string, 13 | options?: Record, 14 | baseOffset?: number, 15 | } 16 | 17 | export const initialEnv = (initialEnvOptions: InitialEnvOptions): Env => { 18 | const { options = {}, baseOffset = 0 } = initialEnvOptions; 19 | const registerCurrentRangeTarget = () => { /**/ }; 20 | const offsetToPos = (_: VirtualLine[], offset: number) => ({ offset }); 21 | 22 | const state = { 23 | maxOffsetMatchFail: null as null | MatchFail, 24 | maxOffsetMatchContext: null as null | MatchContext, 25 | }; 26 | 27 | return { 28 | options, 29 | // toStringOptions: { 30 | // fullToString: true, 31 | // maxToStringDepth: 5, 32 | // }, 33 | registerCurrentRangeTarget, 34 | offsetToPos, 35 | // onMatchFail, 36 | state, 37 | baseOffset, 38 | }; 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /docs/theme.config.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DocsThemeConfig } from "nextra-theme-docs"; 3 | import { useRouter } from "next/router"; 4 | 5 | const HeadComponent = () => { 6 | const router = useRouter(); 7 | const src = `${router.basePath}/static/lawtext_bundles/browser/lawtext.js`; 8 | const fallbackSrc = "https://yamachig.github.io/Lawtext/static/lawtext_bundles/browser/lawtext.js"; 9 | React.useEffect(() => { 10 | import(/*webpackIgnore: true*/ src) 11 | .catch(() => { 12 | return import(/*webpackIgnore: true*/ fallbackSrc); 13 | }) 14 | .catch(console.error); 15 | }, [src]); 16 | return null; 17 | }; 18 | 19 | const config: DocsThemeConfig = { 20 | logo: Lawtext documentation, 21 | head: HeadComponent, 22 | editLink: { component: () => null }, 23 | feedback: { content: () => null }, 24 | footer: { text: () => (
25 | © 2017-{new Date().getFullYear()} yamachi 26 |
) }, 27 | faviconGlyph: "L", 28 | gitTimestamp: () => null, 29 | useNextSeoProps: () => ({ titleTemplate: "%s - Lawtext" }), 30 | nextThemes: { 31 | defaultTheme: "light", 32 | }, 33 | }; 34 | 35 | export default config; 36 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/lexical.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-irregular-whitespace */ 2 | import { irohaChars } from "../../../law/num"; 3 | import { factory } from "../factory"; 4 | 5 | export const ptn$_ = "[  \t]*"; 6 | export const $_ = factory 7 | .withName("OPTIONAL_WHITESPACES") 8 | .regExp(new RegExp(`^${ptn$_}`)) 9 | ; 10 | 11 | export const ptn$__ = "[  \t]+"; 12 | export const $__ = factory 13 | .withName("WHITESPACES") 14 | .regExp(new RegExp(`^${ptn$__}`)) 15 | ; 16 | 17 | export const ptn$_EOL = "[  \t]*\r?\n"; 18 | export const $_EOL = factory 19 | .withName("OPTIONAL_WHITESPACES_AND_EOL") 20 | .regExp(new RegExp(`^${ptn$_EOL}`)) 21 | ; 22 | 23 | export const kanjiDigits = "〇一二三四五六七八九十百千"; 24 | export const $kanjiDigits = factory 25 | .withName("kanjiDigits") 26 | .regExp(new RegExp(`^[${kanjiDigits}]+`)) 27 | ; 28 | 29 | export const arabicDigits = "01234567890123456789"; 30 | export const $arabicDigits = factory 31 | .withName("arabicDigits") 32 | .regExp(new RegExp(`^[${arabicDigits}]+`)) 33 | ; 34 | 35 | export const romanDigits = "iIiIvVvVxXxX"; 36 | export const $romanDigits = factory 37 | .withName("romanDigits") 38 | .regExp(new RegExp(`^[${romanDigits}]+`)) 39 | ; 40 | 41 | export const $irohaChar = factory 42 | .withName("irohaChar") 43 | .regExp(new RegExp(`^[${irohaChars}]`)) 44 | ; 45 | 46 | -------------------------------------------------------------------------------- /core/src/node/el/xmlToEL.ts: -------------------------------------------------------------------------------- 1 | import { DOMParser, Node, Element } from "@xmldom/xmldom"; 2 | import { EL } from "."; 3 | 4 | 5 | export const elementToEL = (el: Element | HTMLElement): EL => { 6 | const children: Array = []; 7 | for (const node of Array.from(el.childNodes)) { 8 | if (node.nodeType === Node.TEXT_NODE) { 9 | const text = (node.nodeValue || "") 10 | .replace(/^[ \r\n\t]+/, "") 11 | .replace(/[ \r\n\t]+$/, ""); 12 | if (text) { 13 | children.push(text); 14 | } 15 | } else if (node.nodeType === Node.ELEMENT_NODE) { 16 | children.push(elementToEL(node as Element | HTMLElement)); 17 | } else { 18 | // console.log(node); 19 | } 20 | } 21 | const attr: Record = {}; 22 | for (const at of Array.from(el.attributes as NamedNodeMap)) { 23 | attr[at.name] = at.value; 24 | } 25 | return new EL( 26 | el.tagName, 27 | attr, 28 | children, 29 | ); 30 | }; 31 | 32 | export const xmlToEL = (xml: string): EL => { 33 | const parser = new DOMParser(); 34 | const dom = parser.parseFromString(xml, "text/xml"); 35 | if (!dom.documentElement) throw new Error("never"); 36 | return elementToEL(dom.documentElement); 37 | }; 38 | 39 | export default xmlToEL; 40 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2022", 5 | "downlevelIteration": true, 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "lib": ["es2023", "dom", "scripthost"], 11 | 12 | // "composite": true, 13 | // "declaration": true, 14 | 15 | "jsx": "react", 16 | 17 | "baseUrl": "./", 18 | "outDir": "dist", 19 | "paths": { 20 | "@appsrc/*": ["src/*"] 21 | }, 22 | "rootDir": "./", 23 | // "rootDirs": ["src", "bin", "test"], 24 | 25 | "skipLibCheck": true, 26 | 27 | "alwaysStrict": true, 28 | "forceConsistentCasingInFileNames": true, 29 | "noFallthroughCasesInSwitch": true, 30 | "noImplicitAny": true, 31 | "noImplicitReturns": true, 32 | "noImplicitThis": true, 33 | "noImplicitOverride": true, 34 | "noUnusedLocals": true, 35 | // "noUnusedParameters": true, 36 | "strict": true, 37 | "strictBindCallApply": true, 38 | "strictFunctionTypes": true, 39 | "strictNullChecks": true, 40 | "strictPropertyInitialization": true, 41 | 42 | "experimentalDecorators": true, 43 | "emitDecoratorMetadata": true, 44 | }, 45 | } -------------------------------------------------------------------------------- /coverage/src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | Lawtext dashboard 29 | 30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /coverage/src/update/worker.ts: -------------------------------------------------------------------------------- 1 | // import formatXML from "xml-formatter"; 2 | import { FSStoredLoader } from "lawtext/dist/src/data/loaders/FSStoredLoader"; 3 | import type { BaseLawInfo } from "lawtext/dist/src/data/lawinfo"; 4 | import { connect } from "../connection"; 5 | import config from "../config"; 6 | import { isMainThread, workerData, parentPort } from "worker_threads"; 7 | import { update } from "./update"; 8 | 9 | 10 | const run = async (): Promise => { 11 | const db = await connect(config.MONGODB_URI); 12 | const loader = new FSStoredLoader(config.DATA_PATH); 13 | const maxDiffLength = workerData.maxDiffLength as number; 14 | 15 | parentPort?.on("message", async (msg) => { 16 | const lawInfo = msg.lawInfo as BaseLawInfo; 17 | try { 18 | await update(lawInfo, maxDiffLength, db, loader); 19 | } catch (e) { 20 | parentPort?.postMessage({ 21 | error: true, 22 | message: { 23 | message: (e as Error).message ?? "", 24 | name: (e as Error).name ?? "", 25 | stack: (e as Error).stack ?? "", 26 | }, 27 | lawInfo, 28 | }); 29 | return; 30 | } 31 | parentPort?.postMessage({ finished: true }); 32 | }); 33 | 34 | parentPort?.postMessage({ ready: true }); 35 | }; 36 | 37 | if (!isMainThread) run(); 38 | 39 | -------------------------------------------------------------------------------- /app/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | export default defineConfig({ 4 | testMatch: "test/**/*.spec.ts", 5 | outputDir: "playwright-results", 6 | fullyParallel: true, 7 | forbidOnly: Boolean(process.env.CI), 8 | retries: process.env.CI ? 2 : 0, 9 | workers: process.env.CI ? 1 : undefined, 10 | timeout: 60000, 11 | use: { 12 | baseURL: "http://127.0.0.1:8091", 13 | trace: "on-first-retry", 14 | }, 15 | reporter: [["html", { host: "127.0.0.1" }]], 16 | projects: [ 17 | { 18 | name: "chromium", 19 | use: { ...devices["Desktop Chrome"] }, 20 | }, 21 | { 22 | name: "firefox", 23 | use: { ...devices["Desktop Firefox"] }, 24 | }, 25 | { 26 | name: "webkit", 27 | use: { ...devices["Desktop Safari"] }, 28 | }, 29 | { 30 | name: "Mobile Chrome", 31 | use: { ...devices["Pixel 5"] }, 32 | }, 33 | { 34 | name: "Mobile Safari", 35 | use: { ...devices["iPhone 12"] }, 36 | }, 37 | ], 38 | webServer: { 39 | command: "npx webpack serve --env DEV_SERVER=1 DEV_SERVER_PORT=8091 --mode development --color --config ./webpack-configs/client.ts", 40 | url: "http://127.0.0.1:8091", 41 | reuseExistingServer: !process.env.CI, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /core/src/renderer/common/docx/getPdfjs.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line tsdoc/syntax 2 | /** @type {() => Promise} */ 3 | const getPdfjs = async () => { 4 | // eslint-disable-next-line tsdoc/syntax 5 | /** @type {() => Promise} */ 6 | let pdfjs; 7 | try { 8 | pdfjs = await eval("import(\"./pdfjs/pdf.min.mjs\")"); 9 | } catch { 10 | try { 11 | pdfjs = await eval("import(\"./pdfjs/pdf.mjs\")"); 12 | } catch { 13 | try { 14 | pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs"); 15 | } catch { 16 | pdfjs = await eval("import(\"pdfjs-dist/legacy/build/pdf.mjs\")"); 17 | } 18 | } 19 | } 20 | 21 | try { 22 | void await eval("import(\"./pdfjs/pdf.worker.min.mjs\")"); 23 | pdfjs.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs"; 24 | } catch { 25 | try { 26 | void await eval("import(\"./pdfjs/pdf.worker.mjs\")"); 27 | } catch { 28 | try { 29 | void await eval("import(\"pdfjs-dist/legacy/build/pdf.worker.mjs\")"); 30 | } catch { 31 | pdfjs.GlobalWorkerOptions.workerSrc = "../core/node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs"; 32 | } 33 | } 34 | } 35 | 36 | 37 | return pdfjs; 38 | }; 39 | exports.getPdfjs = getPdfjs; 40 | -------------------------------------------------------------------------------- /app/src/lawdata/saveListJson.ts: -------------------------------------------------------------------------------- 1 | import type { BaseLawInfo } from "lawtext/dist/src/data/lawinfo"; 2 | import { storedLoader } from "./loaders"; 3 | 4 | 5 | export const saveListJson = async ( 6 | onProgress: (ratio: number, message: string) => void = () => undefined, 7 | ): Promise => { 8 | 9 | const progress = (() => { 10 | let currentRatio = 0; 11 | let currentMessage = ""; 12 | return (ratio?: number, message?: string) => { 13 | currentRatio = ratio || currentRatio; 14 | currentMessage = message || currentMessage; 15 | onProgress(currentRatio, currentMessage); 16 | }; 17 | })(); 18 | 19 | progress(0, "Loading CSV..."); 20 | 21 | console.log("\nListing up XMLs..."); 22 | let infos: BaseLawInfo[]; 23 | try { 24 | infos = await storedLoader.loadBaseLawInfosFromCSV(); 25 | } catch (e) { 26 | console.error("CSV list cannot be fetched."); 27 | console.error((e as Error).message, (e as Error).stack); 28 | return null; 29 | } 30 | 31 | console.log(`Processing ${infos.length} XMLs...`); 32 | 33 | const list = await storedLoader.makeLawListFromBaseLawInfos(infos, onProgress); 34 | progress(undefined, "Generating json..."); 35 | const json = JSON.stringify(list); 36 | progress(undefined, "Saving json..."); 37 | const blob = new Blob( 38 | [json], 39 | { type: "application/json" }, 40 | ); 41 | return blob; 42 | }; 43 | -------------------------------------------------------------------------------- /app/src/components/LawView/ErrorCatcher.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const ErrorComponentDiv = styled.div` 5 | `; 6 | 7 | export class ErrorCatcher extends React.Component void}>, { hasError: boolean, error: Error | null }> { 8 | constructor(props: { onError: (error: Error) => void}) { 9 | super(props); 10 | this.state = { hasError: false, error: null }; 11 | } 12 | 13 | public override componentDidCatch(error: Error): void { 14 | this.setState(Object.assign({}, this.state, { hasError: true, error })); 15 | if (this.props.onError) this.props.onError(error); 16 | } 17 | 18 | public override render(): React.JSX.Element | React.JSX.Element[] | null | undefined { 19 | if (this.state.hasError) { 20 | return this.renderError(); 21 | } else { 22 | return this.renderNormal(); 23 | } 24 | } 25 | 26 | protected renderNormal(): React.JSX.Element | React.JSX.Element[] | null | undefined { 27 | return <>{this.props.children}; 28 | } 29 | 30 | protected renderError(): React.JSX.Element | React.JSX.Element[] | null | undefined { 31 | return ( 32 | 33 | エラーが発生しました: 34 | {this.state.error && this.state.error.toString()} 35 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | Lawtext 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /coverage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "downlevelIteration": true, 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "lib": ["es2017", "dom", "scripthost"], 11 | 12 | // "composite": true, 13 | "allowJs": true, 14 | "declaration": true, 15 | 16 | "jsx": "react", 17 | 18 | "baseUrl": "./", 19 | "outDir": "dist", 20 | // "paths": { 21 | // "@coveragesrc/*": ["src/*"], 22 | // "@coresrc/*": ["../core/src/*"] 23 | // }, 24 | "rootDir": "./", 25 | // "rootDirs": ["src", "bin", "test"], 26 | 27 | "skipLibCheck": true, 28 | 29 | "alwaysStrict": true, 30 | "forceConsistentCasingInFileNames": true, 31 | "noFallthroughCasesInSwitch": true, 32 | "noImplicitAny": true, 33 | "noImplicitReturns": true, 34 | "noImplicitThis": true, 35 | "noImplicitOverride": true, 36 | "noUnusedLocals": true, 37 | // "noUnusedParameters": true, 38 | "strict": true, 39 | "strictBindCallApply": true, 40 | "strictFunctionTypes": true, 41 | "strictNullChecks": true, 42 | "strictPropertyInitialization": true, 43 | 44 | "experimentalDecorators": true, 45 | "emitDecoratorMetadata": true, 46 | }, 47 | } -------------------------------------------------------------------------------- /core/webpack-configs/bundle-node.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /* eslint-disable @stylistic/js/quote-props */ 3 | const path = require("path"); 4 | const webpack = require("webpack"); 5 | 6 | const rootDir = path.dirname(__dirname); 7 | 8 | /** @returns {import("webpack").Configuration} */ 9 | module.exports = (env, argv) => { 10 | const distDir = path.resolve( 11 | rootDir, 12 | (argv.mode === "development") ? "dist-bundle-dev" : "dist-bundle-prod", 13 | ); 14 | return { 15 | target: "node", 16 | mode: (argv.mode === "development") ? "development" : "production", 17 | entry: [path.resolve(rootDir, "./src/main.ts")], 18 | output: { 19 | filename: "node/lawtext_cli.js", 20 | path: distDir, 21 | library: { 22 | name: "lawtext", 23 | type: "umd", 24 | }, 25 | }, 26 | resolve: { 27 | extensions: [".ts", ".tsx", ".js", ".json"], 28 | alias: { 29 | "canvas": false, 30 | "pdfjs-dist": false, 31 | }, 32 | fallback: { 33 | "path": require.resolve("path-browserify"), 34 | }, 35 | }, 36 | module: { 37 | rules: [{ test: /\.tsx?$/, use: "ts-loader" }], 38 | }, 39 | plugins: [ 40 | new webpack.optimize.LimitChunkCountPlugin({ 41 | maxChunks: 1, 42 | }), 43 | ], 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /coverage/src/client/index.tsx: -------------------------------------------------------------------------------- 1 | import { library } from "@fortawesome/fontawesome-svg-core"; 2 | import { 3 | faArrowAltCircleLeft, 4 | faArrowAltCircleRight, 5 | faArrowLeft, 6 | faArrowRight, 7 | faBan, 8 | faCheckCircle, 9 | faEllipsisV, 10 | faExchangeAlt, 11 | faExclamationTriangle, 12 | faExternalLinkAlt, 13 | faMinus, 14 | faTimes, 15 | } from "@fortawesome/free-solid-svg-icons"; 16 | import "bootstrap"; 17 | import * as moment from "moment"; 18 | import * as React from "react"; 19 | import * as ReactDOM from "react-dom/client"; 20 | import { Route, Routes } from "react-router-dom"; 21 | import { HashRouter } from "react-router-dom"; 22 | import { LawtextDashboardPage } from "./components/LawtextDashboardPage"; 23 | import "./index.scss"; 24 | moment.locale("ja"); 25 | 26 | library.add( 27 | faArrowAltCircleLeft, 28 | faArrowAltCircleRight, 29 | faArrowLeft, 30 | faArrowRight, 31 | faBan, 32 | faCheckCircle, 33 | faEllipsisV, 34 | faExchangeAlt, 35 | faExclamationTriangle, 36 | faExternalLinkAlt, 37 | faMinus, 38 | faTimes, 39 | ); 40 | 41 | const rootEL = document.getElementById("root"); 42 | if (rootEL) { 43 | const root = ReactDOM.createRoot(rootEL); 44 | root.render(( 45 | 46 | 47 | } /> 48 | } /> 49 | 50 | 51 | )); 52 | } 53 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "downlevelIteration": true, 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "lib": [ 11 | "es2017", 12 | "dom", 13 | "scripthost" 14 | ], 15 | // "composite": true, 16 | // "declaration": true, 17 | "jsx": "preserve", 18 | "baseUrl": "./", 19 | "outDir": "dist", 20 | "rootDir": "./", 21 | // "rootDirs": ["src", "bin", "test"], 22 | "skipLibCheck": true, 23 | "alwaysStrict": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "noFallthroughCasesInSwitch": true, 26 | "noImplicitAny": true, 27 | "noImplicitReturns": true, 28 | "noImplicitThis": true, 29 | "noImplicitOverride": true, 30 | "noUnusedLocals": true, 31 | // "noUnusedParameters": true, 32 | "strict": true, 33 | "strictBindCallApply": true, 34 | "strictFunctionTypes": true, 35 | "strictNullChecks": true, 36 | "strictPropertyInitialization": true, 37 | "experimentalDecorators": true, 38 | "emitDecoratorMetadata": true, 39 | "allowJs": true, 40 | "noEmit": true, 41 | "incremental": true, 42 | "isolatedModules": true 43 | }, 44 | "include": [ 45 | "next-env.d.ts", 46 | "**/*.ts", 47 | "**/*.js", 48 | "**/*.mjs", 49 | "**/*.tsx" 50 | ], 51 | "exclude": [ 52 | "node_modules", 53 | "./.next", 54 | "./out" 55 | ], 56 | } 57 | -------------------------------------------------------------------------------- /core/src/node/cst/inline.ts: -------------------------------------------------------------------------------- 1 | import type * as std from "../../law/std"; 2 | import { isControl } from "../../law/std"; 3 | import type { Diff } from "../../util"; 4 | import type { EL } from "../el"; 5 | 6 | export class Control { 7 | public constructor( 8 | public control: string, 9 | public controlRange: [start: number, end: number] | null, 10 | public trailingSpace: string, 11 | public trailingSpaceRange: [start: number, end: number] | null, 12 | ) {} 13 | } 14 | 15 | export type Controls = Control[]; 16 | 17 | export class AttrEntry { 18 | public constructor( 19 | public entryText: string, 20 | public entry: [name: string, value: string], 21 | public entryRange: [start: number, end: number] | null, 22 | public trailingSpace: string, 23 | public trailingSpaceRange: [start: number, end: number] | null, 24 | ) {} 25 | } 26 | 27 | export type AttrEntries = AttrEntry[]; 28 | 29 | export class Sentences { 30 | public constructor( 31 | public leadingSpace: string, 32 | public leadingSpaceRange: [start: number, end: number] | null, 33 | public attrEntries: AttrEntries, 34 | public sentences: std.Sentence[], 35 | ) {} 36 | } 37 | 38 | export type SentencesArray = Sentences[]; 39 | 40 | export type SentenceChildEL = Diff; 41 | export const isSentenceChildEL = (el: EL): el is SentenceChildEL => isControl(el) || ["Line", "QuoteStruct", "ArithFormula", "Ruby", "Sup", "Sub"].includes(el.tag); 42 | -------------------------------------------------------------------------------- /core/src/renderer/rules/supplNote.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type * as std from "../../law/std"; 3 | import type { HTMLComponentProps } from "../common/html"; 4 | import { elProps, wrapHTMLComponent } from "../common/html"; 5 | import { DOCXSentenceChildrenRun, HTMLSentenceChildrenRun } from "./sentenceChildrenRun"; 6 | import type { DOCXComponentProps } from "../common/docx/component"; 7 | import { wrapDOCXComponent } from "../common/docx/component"; 8 | import { w } from "../common/docx/tags"; 9 | 10 | 11 | export interface SupplNoteProps { 12 | el: std.SupplNote, 13 | indent: number, 14 | } 15 | 16 | export const HTMLSupplNoteCSS = /*css*/` 17 | .suppl-note { 18 | clear: both; 19 | } 20 | `; 21 | 22 | export const HTMLSupplNote = wrapHTMLComponent("HTMLSupplNote", ((props: HTMLComponentProps & SupplNoteProps) => { 23 | 24 | const { el, htmlOptions, indent } = props; 25 | 26 | return ( 27 |
28 | 29 |
30 | ); 31 | })); 32 | 33 | export const DOCXSupplNote = wrapDOCXComponent("DOCXSupplNote", ((props: DOCXComponentProps & SupplNoteProps) => { 34 | 35 | const { el, docxOptions, indent } = props; 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | })); 46 | -------------------------------------------------------------------------------- /core/src/renderer/html.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EL } from "../node/el"; 3 | import * as std from "../law/std"; 4 | import { HTMLLaw } from "./rules/law"; 5 | import htmlCSS from "./rules/htmlCSS"; 6 | import { renderToStaticMarkup } from "./common"; 7 | import { HTMLAnyELs } from "./rules/any"; 8 | import type { HTMLOptions } from "./common/html"; 9 | import loadEL from "../node/el/loadEL"; 10 | import type { JsonEL } from "../node/el/jsonEL"; 11 | 12 | export const renderHTML = (elOrJsonEL: JsonEL | EL, htmlOptions?: HTMLOptions): string => { 13 | const rendered = renderHTMLfragment(elOrJsonEL, htmlOptions); 14 | const html = /*html*/`\ 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | ${rendered} 25 | 26 | 27 | `; 28 | return html; 29 | }; 30 | 31 | export const renderHTMLfragment = (elOrJsonEL: JsonEL | EL, htmlOptions?: HTMLOptions): string => { 32 | const el = elOrJsonEL instanceof EL ? elOrJsonEL : loadEL(elOrJsonEL); 33 | const element = std.isLaw(el) 34 | ? 35 | : ; 36 | const rendered = renderToStaticMarkup(element); 37 | return rendered; 38 | }; 39 | 40 | export const renderElementsFragment = (elements: (JsonEL | EL)[], htmlOptions?: HTMLOptions): string => { 41 | return elements.map(e => renderHTMLfragment(e, htmlOptions)).join("\n"); 42 | }; 43 | 44 | export default renderHTML; 45 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/$squareAttr.ts: -------------------------------------------------------------------------------- 1 | import { AttrEntry } from "../../../node/cst/inline"; 2 | import { factory } from "../factory"; 3 | import type { ValueRule, WithErrorRule } from "../util"; 4 | 5 | export const makeSquareAttrRule = (lazyNameRule: (f: typeof factory) => ValueRule): WithErrorRule => { 6 | return factory 7 | .withName("squareAttr") 8 | .sequence(c => c 9 | .and(r => r.seqEqual("[")) 10 | .and(lazyNameRule, "name") 11 | .and(r => r.seqEqual("=\"")) 12 | .and(r => r 13 | .asSlice(r => r 14 | // eslint-disable-next-line no-irregular-whitespace 15 | .oneOrMore(r => r.regExp(/^[^  \t\r\n\]"]/)) 16 | ) 17 | , "value") 18 | .and(r => r.seqEqual("\"]")) 19 | .action(({ name, value, text, range }) => { 20 | const r = range(); 21 | return { 22 | value: new AttrEntry( 23 | text(), 24 | [name, value], 25 | r, 26 | "", 27 | [r[1], r[1]], 28 | ), 29 | errors: [], 30 | }; 31 | }) 32 | ); 33 | }; 34 | 35 | export const $squareAttr = makeSquareAttrRule(r => r 36 | .asSlice(r => r 37 | .oneOrMore(r => r 38 | // eslint-disable-next-line no-irregular-whitespace 39 | .regExp(/^[^  \t\r\n\]=]/), 40 | ), 41 | ), 42 | ); 43 | 44 | export default $squareAttr; 45 | -------------------------------------------------------------------------------- /docs/pages/cli.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CLI usage 3 | --- 4 | 5 | # CLI usage 6 | 7 | ## How to use in browsers 8 | 9 | ## Option 1: Try it out here 10 | 11 | - On this page, open the browser console (for Chrome, press `Ctrl+Shift+J` (Windows/Linux) or `Cmd+Opt+J` (Mac)). 12 | - Run `console.log(lawtext.run.help)` in the browser console. 13 | 14 | ## Option 2: Try in other places 15 | 16 | - Copy the URL of [lawtext.js](/static/lawtext_bundles/browser/lawtext.js). 17 | - Open the browser console (for Chrome, press `Ctrl+Shift+J` (Windows/Linux) or `Cmd+Opt+J` (Mac)). 18 | - Run `await import("...")` (replace `...` with the copied URL of [lawtext.js](/static/lawtext_bundles/browser/lawtext.js)) in the browser console. 19 | - Run `console.log(lawtext.run.help)` in the browser console. 20 | 21 | ## How to use in Node.js 22 | 23 | - Prerequisites: [Node.js](https://nodejs.org/) 24 | 25 | ### Option 1: Run the prebuilt bundle 26 | 27 | - Download the prebuilt bundle from here: [lawtext_cli.js](/static/lawtext_bundles/node/lawtext_cli.js) 28 | - Run `node lawtext_cli.js --help` 29 | 30 | ### Option 2: Run using `npx` globally 31 | 32 | - Run `npx lawtext --help` 33 | 34 | ### Option 3: Run as a package 35 | 36 | - Run the following command in your working directory (or just an empty directory). 37 | ``` 38 | npm install lawtext 39 | ``` 40 | - Run `npx lawtext --help` 41 | 42 | ### Option 4: Run independently 43 | 44 | - Clone or download [the Lawtext repository](https://github.com/yamachig/Lawtext), move to the `core` directory, and run `npm install`. 45 | - Run `npm run lawtext -- --help` (note that you need `--` before the lawtext options.) -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2022", 5 | "downlevelIteration": true, 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "lib": ["es2023", "scripthost"], 11 | "jsx": "react", 12 | 13 | "composite": true, 14 | "allowJs": true, 15 | "declaration": true, 16 | 17 | "baseUrl": "./", 18 | "outDir": "dist", 19 | // "paths": {"@coresrc/*": ["src/*"]}, 20 | // "rootDirs": ["src", "bin", "test"], 21 | 22 | "skipLibCheck": true, 23 | 24 | "alwaysStrict": true, 25 | "forceConsistentCasingInFileNames": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitAny": true, 28 | "noImplicitReturns": true, 29 | "noImplicitThis": true, 30 | "noImplicitOverride": true, 31 | "noUnusedLocals": true, 32 | "noUnusedParameters": true, 33 | "strict": true, 34 | "strictBindCallApply": true, 35 | "strictFunctionTypes": true, 36 | "strictNullChecks": true, 37 | "strictPropertyInitialization": true, 38 | 39 | "experimentalDecorators": true, 40 | "emitDecoratorMetadata": true, 41 | }, 42 | "include": [ 43 | "src/**/*", 44 | "src/**/*.json", 45 | "bin/**/*", 46 | "test/**/*" 47 | ], 48 | "exclude": [ 49 | "**/* copy.*", 50 | "./node_modules", 51 | "./mochawesome-report", 52 | "./dist", 53 | "./data", 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /core/src/node/el/controls/varRef.ts: -------------------------------------------------------------------------------- 1 | import { EL } from ".."; 2 | import type { SentenceTextRange } from "../../container/sentenceEnv"; 3 | 4 | 5 | export interface VarRefOptions { 6 | refName: string, 7 | declarationID: string, 8 | refSentenceTextRange: SentenceTextRange, 9 | range: [start: number, end: number] | null, 10 | } 11 | 12 | export class ____VarRef extends EL { 13 | public override tag = "____VarRef" as const; 14 | public override get isControl(): true { return true; } 15 | public override attr: { 16 | refName: string, 17 | declarationID: string, 18 | refSentenceTextRange: string, 19 | }; 20 | 21 | private refSentenceTextRangeCache: [str: string, value: SentenceTextRange] | null = null; 22 | public get refSentenceTextRange(): SentenceTextRange { 23 | if (this.refSentenceTextRangeCache !== null && this.refSentenceTextRangeCache[0] === this.attr.refSentenceTextRange) { 24 | return this.refSentenceTextRangeCache[1]; 25 | } else { 26 | const refSentenceTextRange = JSON.parse(this.attr.refSentenceTextRange) as SentenceTextRange; 27 | this.refSentenceTextRangeCache = [this.attr.refSentenceTextRange, refSentenceTextRange]; 28 | return refSentenceTextRange; 29 | } 30 | } 31 | 32 | constructor(options: VarRefOptions) { 33 | super("____VarRef", {}, [], options.range); 34 | 35 | const { refName, declarationID, refSentenceTextRange } = options; 36 | 37 | this.attr = { 38 | refName, 39 | declarationID, 40 | refSentenceTextRange: JSON.stringify(refSentenceTextRange), 41 | }; 42 | 43 | this.children.push(refName); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/components/useObserved.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 4 | export const useObserved = () => { 5 | 6 | const [observed, setObserved] = React.useState(false); 7 | 8 | const observedRef = React.useRef(null); 9 | const observerRef = React.useRef(null); 10 | 11 | const unobserve = React.useCallback(() => { 12 | const target = observedRef.current; 13 | const observer = observerRef.current; 14 | if (target && observer) observer.unobserve(target); 15 | }, []); 16 | 17 | const observe = React.useCallback(() => { 18 | const target = observedRef.current; 19 | const observer = observerRef.current; 20 | if (!target) { 21 | console.error("useObserved: no ref found"); 22 | return; 23 | } 24 | if (observer) observer.observe(target); 25 | }, []); 26 | 27 | const forceObserved = React.useCallback(() => { 28 | unobserve(); 29 | setObserved(true); 30 | }, [unobserve]); 31 | 32 | React.useEffect(() => { 33 | observerRef.current = new IntersectionObserver(entries => { 34 | for (const entry of entries) { 35 | if (entry.intersectionRatio > 0) { 36 | unobserve(); 37 | setObserved(true); 38 | return; 39 | } 40 | } 41 | }); 42 | }, [unobserve]); 43 | 44 | React.useEffect(() => { 45 | observe(); 46 | return unobserve; 47 | }, [observe, unobserve]); 48 | 49 | return { 50 | observed, 51 | observedRef, 52 | forceObserved, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /core/bin/saveLawdata.ts: -------------------------------------------------------------------------------- 1 | 2 | import yargs from "yargs"; 3 | import { ProgressBar } from "../src/util/term"; 4 | import * as save_fs from "../src/data/saveFs"; 5 | import { FSStoredLoader } from "../src/data/loaders/FSStoredLoader"; 6 | 7 | const bar = new ProgressBar(); 8 | const progress = bar.progress.bind(bar); 9 | 10 | const download = async (dataDir: string): Promise => { 11 | const loader = new FSStoredLoader(dataDir); 12 | await save_fs.download(loader, progress); 13 | }; 14 | 15 | const saveList = async (dataDir: string): Promise => { 16 | const loader = new FSStoredLoader(dataDir); 17 | await save_fs.saveList(loader, progress); 18 | }; 19 | 20 | const main = async (): Promise => { 21 | 22 | const args = await yargs 23 | .option("mode", { 24 | type: "string", 25 | choices: ["all", "download", "save-list"], 26 | demandOption: false, 27 | default: "all", 28 | alias: "m", 29 | }) 30 | .option("data-dir", { 31 | type: "string", 32 | demandOption: true, 33 | alias: "d", 34 | }) 35 | .argv; 36 | 37 | if (args.mode === "download" || args.mode === "all") { 38 | bar.start(1, 0); 39 | await download(args["data-dir"]); 40 | bar.stop(); 41 | } 42 | if (args.mode === "save-list" || args.mode === "all") { 43 | bar.start(1, 0); 44 | await saveList(args["data-dir"]); 45 | bar.stop(); 46 | } 47 | }; 48 | 49 | if (typeof require !== "undefined" && require.main === module) { 50 | process.on("unhandledRejection", e => { 51 | console.dir(e); 52 | process.exit(1); 53 | }); 54 | main().catch(e => { throw e; }); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /core/src/data/loaders/FetchElawsLoader.ts: -------------------------------------------------------------------------------- 1 | import type { LawInfosStruct } from "./common"; 2 | import { lawInfosToByLawnumAndID, Loader } from "./common"; 3 | import type { BaseLawInfo } from "../lawinfo"; 4 | import { LawInfo } from "../lawinfo"; 5 | import type { ElawsLawData } from "../../elawsApi"; 6 | import { fetchLawNameList, fetchLawData } from "../../elawsApi"; 7 | 8 | const fetchBaseLawInfosFromElaws = async (): Promise => { 9 | const lawNameList = await fetchLawNameList(); 10 | return lawNameList.map(item => { 11 | const baseLawInfo: BaseLawInfo = { 12 | LawID: item.LawId, 13 | LawNum: item.LawNo, 14 | LawTitle: item.LawName, 15 | Enforced: true, 16 | Path: item.LawId, 17 | XmlName: `${item.LawId}.xml`, 18 | }; 19 | return baseLawInfo; 20 | }); 21 | }; 22 | 23 | export class FetchElawsLoader extends Loader { 24 | 25 | public async loadLawInfosStruct(): Promise { 26 | const baseLawInfos = await fetchBaseLawInfosFromElaws(); 27 | const lawInfos = baseLawInfos.map(LawInfo.fromBaseLawInfo); 28 | const [lawInfosByLawnum, lawInfosByLawID] = lawInfosToByLawnumAndID(lawInfos); 29 | return { lawInfos, lawInfosByLawnum, lawInfosByLawID }; 30 | } 31 | 32 | public async loadBaseLawInfosFromCSV(): Promise { 33 | return fetchBaseLawInfosFromElaws(); 34 | } 35 | 36 | public async loadLawXMLStructByInfo(lawInfoOrLawIDOrLawNum: BaseLawInfo | string): Promise { 37 | return fetchLawData(typeof lawInfoOrLawIDOrLawNum === "string" ? lawInfoOrLawIDOrLawNum : lawInfoOrLawIDOrLawNum.LawID); 38 | } 39 | 40 | } 41 | 42 | export default FetchElawsLoader; 43 | -------------------------------------------------------------------------------- /core/src/parser/cst/rules/$lines.ts: -------------------------------------------------------------------------------- 1 | import factory from "../factory"; 2 | import $blankLine from "./$blankLine"; 3 | import $tocHeadLine from "./$tocHeadLine"; 4 | import $articleGroupHeadLine from "./$articleGroupHeadLine"; 5 | import $appdxItemHeadLine from "./$appdxItemHeadLine"; 6 | import $supplProvisionAppdxItemHeadLine from "./$supplProvisionAppdxItemHeadLine"; 7 | import $supplProvisionHeadLine from "./$supplProvisionHeadLine"; 8 | import $articleLine from "./$articleLine"; 9 | import $paragraphItemLine from "./$paragraphItemLine"; 10 | import $tableColumnLine from "./$tableColumnLine"; 11 | import $otherLine from "./$otherLine"; 12 | import type { WithErrorRule } from "../util"; 13 | import type { Line } from "../../../node/cst/line"; 14 | 15 | export const $lines: WithErrorRule = factory.withName("lines") 16 | .sequence(s => s 17 | .and(r => r 18 | .zeroOrMore(r => r 19 | .choice(c => c 20 | .or(() => $blankLine) 21 | .or(() => $tableColumnLine) 22 | .or(() => $tocHeadLine) 23 | .or(() => $articleGroupHeadLine) 24 | .or(() => $paragraphItemLine) 25 | .or(() => $supplProvisionHeadLine) 26 | .or(() => $supplProvisionAppdxItemHeadLine) 27 | .or(() => $appdxItemHeadLine) 28 | .or(() => $articleLine) 29 | .or(() => $otherLine) 30 | ) 31 | ) 32 | , "lines") 33 | .action(({ lines }) => { 34 | return { 35 | value: lines.map(line => line.value), 36 | errors: lines.map(line => line.errors).flat(), 37 | }; 38 | }) 39 | ) 40 | ; 41 | 42 | export default $lines; 43 | 44 | -------------------------------------------------------------------------------- /core/webpack-configs/bundle-browser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /* eslint-disable @stylistic/js/quote-props */ 3 | const path = require("path"); 4 | const webpack = require("webpack"); 5 | const TerserPlugin = require("terser-webpack-plugin"); 6 | 7 | const rootDir = path.dirname(__dirname); 8 | 9 | /** @returns {import("webpack").Configuration} */ 10 | module.exports = (env, argv) => { 11 | const distDir = path.resolve( 12 | rootDir, 13 | (argv.mode === "development") ? "dist-bundle-dev" : "dist-bundle-prod", 14 | ); 15 | return { 16 | mode: (argv.mode === "development") ? "development" : "production", 17 | entry: [path.resolve(rootDir, "./src/lawtext.ts")], 18 | output: { 19 | filename: "browser/lawtext.js", 20 | path: distDir, 21 | library: { 22 | name: "lawtext", 23 | type: "umd", 24 | }, 25 | }, 26 | resolve: { 27 | extensions: [".ts", ".tsx", ".js", ".json"], 28 | alias: { 29 | "node-fetch": false, 30 | "fs": false, 31 | "canvas": false, 32 | "pdfjs-dist": false, 33 | }, 34 | fallback: { 35 | "path": require.resolve("path-browserify"), 36 | }, 37 | }, 38 | module: { 39 | rules: [{ test: /\.tsx?$/, use: "ts-loader" }], 40 | }, 41 | plugins: [ 42 | new webpack.optimize.LimitChunkCountPlugin({ 43 | maxChunks: 1, 44 | }), 45 | ], 46 | optimization: { 47 | minimizer: [ 48 | new TerserPlugin({ 49 | extractComments: false, 50 | }), 51 | ], 52 | }, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /core/test/prepareTest.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { promisify } from "util"; 3 | import { download, saveList } from "../src/data/saveFs"; 4 | import { ProgressBar } from "../src/util/term"; 5 | import { FSStoredLoader } from "../src/data/loaders/FSStoredLoader"; 6 | // import { before } from "mocha"; 7 | import dotenv from "dotenv"; 8 | dotenv.config(); 9 | 10 | const DATA_PATH = process.env["DATA_PATH"]; 11 | 12 | export const __loader = DATA_PATH ? new FSStoredLoader(DATA_PATH) : null; 13 | export const assertLoader = (it: Mocha.Context) => { 14 | if (__loader) return __loader; 15 | it.skip(); 16 | }; 17 | 18 | // before("Run prepare_test", async function() { 19 | // this.timeout(3600_000); 20 | // await prepare(loader); 21 | // }); 22 | 23 | export const prepare = async (loader: FSStoredLoader): Promise => { 24 | const bar = new ProgressBar(); 25 | const progress = bar.progress.bind(bar); 26 | 27 | if (!(await promisify(fs.exists)(loader.lawdataPath))) { 28 | console.log(`Preparing lawdata into ${loader.lawdataPath} ...`); 29 | 30 | bar.start(1, 0); 31 | await download(loader, progress); 32 | bar.stop(); 33 | } 34 | 35 | if (!(await promisify(fs.exists)(loader.listJsonPath))) { 36 | console.log(`Preparing list json into ${loader.listJsonPath} ...`); 37 | 38 | bar.start(1, 0); 39 | await saveList(loader, progress); 40 | bar.stop(); 41 | } 42 | }; 43 | 44 | if (typeof require !== "undefined" && require.main === module) { 45 | console.log("running prepare() from toplevel."); 46 | process.on("unhandledRejection", e => { 47 | // const newErr = new Error(`Unhandled rejection in prepare(): ${e}`); 48 | console.log(); 49 | console.dir(e); 50 | // console.error(newErr); 51 | process.exit(1); 52 | }); 53 | if (__loader) prepare(__loader); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/lawdata/common.ts: -------------------------------------------------------------------------------- 1 | import type { BaseLawtextLawDataProps, BaseXMLLawDataProps, LawDataCore } from "lawtext/dist/src/data/lawdata"; 2 | 3 | export interface StoredLawDataProps extends BaseXMLLawDataProps { 4 | source: "stored", 5 | lawPath: string, 6 | } 7 | export const isStoredLawDataProps = (props: LawDataProps): 8 | props is StoredLawDataProps => 9 | props.source === "stored"; 10 | 11 | export interface ElawsLawDataProps extends BaseXMLLawDataProps { 12 | source: "elaws", 13 | } 14 | export const isElawsLawDataProps = (props: LawDataProps): 15 | props is ElawsLawDataProps => 16 | props.source === "elaws"; 17 | 18 | export interface TempXMLLawDataProps extends BaseXMLLawDataProps { 19 | source: "temp_xml", 20 | } 21 | export const isTempXMLLawDataProps = (props: LawDataProps): 22 | props is TempXMLLawDataProps => 23 | props.source === "temp_xml"; 24 | 25 | export interface FileXMLLawDataProps extends BaseXMLLawDataProps { 26 | source: "file_xml", 27 | } 28 | export const isFileXMLLawDataProps = (props: LawDataProps): 29 | props is FileXMLLawDataProps => 30 | props.source === "file_xml"; 31 | 32 | export interface TempLawtextLawDataProps extends BaseLawtextLawDataProps { 33 | source: "temp_lawtext", 34 | } 35 | export const isTempLawtextLawDataProps = (props: LawDataProps): 36 | props is TempLawtextLawDataProps => 37 | props.source === "temp_lawtext"; 38 | 39 | export interface FileLawtextLawDataProps extends BaseLawtextLawDataProps { 40 | source: "file_lawtext", 41 | } 42 | export const isFileLawtextLawDataProps = (props: LawDataProps): 43 | props is FileLawtextLawDataProps => 44 | props.source === "file_lawtext"; 45 | 46 | export type LawDataProps = StoredLawDataProps | ElawsLawDataProps | TempXMLLawDataProps | FileXMLLawDataProps | TempLawtextLawDataProps | FileLawtextLawDataProps; 47 | 48 | export type LawData = LawDataProps & LawDataCore; 49 | -------------------------------------------------------------------------------- /app/src/components/LawtextAppPageState.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { LawData } from "@appsrc/lawdata/common"; 3 | import { useNavigate, useParams } from "react-router-dom"; 4 | 5 | export interface BaseLawtextAppPageState { 6 | law: LawData | null; 7 | loadingLaw: boolean; 8 | viewerMessages: Record; 9 | hasError: boolean; 10 | errors: Error[]; 11 | navigatedPath: string; 12 | } 13 | 14 | const getInitialState = (): BaseLawtextAppPageState => ({ 15 | law: null, 16 | loadingLaw: false, 17 | viewerMessages: {}, 18 | hasError: false, 19 | errors: [], 20 | navigatedPath: "", 21 | }); 22 | export type SetLawtextAppPageState = (newState: Partial) => void; 23 | export type OrigSetLawtextAppPageState = React.Dispatch>; 24 | 25 | export interface LawtextAppPageStateStruct { 26 | origState: Readonly, 27 | origSetState: OrigSetLawtextAppPageState, 28 | setState: SetLawtextAppPageState, 29 | navigate: ReturnType, 30 | path: string, 31 | } 32 | interface RouteParams { 33 | "*": string | undefined, 34 | [key: string]: string | undefined, 35 | } 36 | 37 | export const useLawtextAppPageState = (): LawtextAppPageStateStruct => { 38 | 39 | const { "*": path } = useParams(); 40 | 41 | const [state, origSetState] = React.useState(getInitialState); 42 | 43 | const setState = React.useCallback( 44 | (newState: Partial) => { 45 | origSetState(prevState => ({ ...prevState, ...newState })); 46 | }, 47 | [origSetState], 48 | ); 49 | const navigate = useNavigate(); 50 | 51 | return { 52 | origState: state, 53 | origSetState, 54 | setState, 55 | navigate, 56 | path: path ?? "", 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /coverage/src/client/components/LawtextDashboardPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | import type { LawtextDashboardPageStateStruct } from "./LawtextDashboardPageState"; 4 | import { useLawtextDashboardPageState } from "./LawtextDashboardPageState"; 5 | import MainPanel from "./MainPanel"; 6 | import SidePanel from "./SidePanel"; 7 | 8 | 9 | const ViewerLoadingTag = styled.div` 10 | position: fixed; 11 | top: 0; 12 | right: 0; 13 | bottom: 0; 14 | left: 0; 15 | z-index: 100; 16 | 17 | padding-top: 1rem; 18 | text-align: center; 19 | 20 | pointer-events: none; 21 | `; 22 | export const ViewerLoading: React.FC = () => { 23 | return ( 24 | 25 |
26 | ロード中です… 27 |
28 |
29 | ); 30 | }; 31 | 32 | 33 | const ViewerInitialTag = styled.div` 34 | display: flex; 35 | position: fixed; 36 | top: 0; 37 | right: 0; 38 | bottom: 0; 39 | left: 0; 40 | `; 41 | 42 | export const ViewerInitial: React.FC = props => { 43 | return ( 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export const LawtextDashboardPage: React.FC = () => { 52 | const stateStruct = useLawtextDashboardPageState(); 53 | 54 | const { onNavigated, origState, routeParams } = stateStruct; 55 | 56 | React.useEffect(() => { 57 | onNavigated(); 58 | // eslint-disable-next-line react-hooks/exhaustive-deps 59 | }, [routeParams]); 60 | 61 | return ( 62 |
63 | {(origState.loading > 0) && 64 | 65 | } 66 | 67 |
68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /core/src/parser/cst/env.ts: -------------------------------------------------------------------------------- 1 | import type { MatchFail, MatchContext, StringPos, BaseEnv } from "generic-parser/lib/core"; 2 | import { getMemorizedStringOffsetToPos } from "generic-parser/lib/core"; 3 | import type { WithErrorRule } from "./util"; 4 | import type { SentenceChildEL } from "../../node/cst/inline"; 5 | import { makeReOutsideParenthesesTextChars, makeReParenthesesInlineTextChars, makeRePeriodSentenceTextChars } from "./rules/$sentenceChildren"; 6 | 7 | export interface Env extends BaseEnv { 8 | currentIndentDepth: number; 9 | state: { 10 | parenthesesDepth: number; 11 | maxOffsetMatchFail: MatchFail | null; 12 | maxOffsetMatchContext: MatchContext | null; 13 | }; 14 | options: Record & Partial<{ 15 | reParenthesesInlineTextChars: RegExp, 16 | reOutsideParenthesesTextChars: RegExp, 17 | rePeriodSentenceTextChars: RegExp, 18 | inlineTokenRule: WithErrorRule, 19 | }>, 20 | } 21 | 22 | export interface InitialEnvOptions { 23 | options?: Env["options"], 24 | baseOffset?: number, 25 | } 26 | 27 | export const initialEnv = (initialEnvOptions: InitialEnvOptions): Env => { 28 | const { options, baseOffset = 0 } = initialEnvOptions; 29 | const offsetToPos = getMemorizedStringOffsetToPos(); 30 | 31 | const state = { 32 | parenthesesDepth: 0, 33 | maxOffsetMatchFail: null as null | MatchFail, 34 | maxOffsetMatchContext: null as null | MatchContext, 35 | }; 36 | 37 | return { 38 | currentIndentDepth: 0, 39 | offsetToPos, 40 | registerCurrentRangeTarget: () => { /**/ }, 41 | options: { 42 | reParenthesesInlineTextChars: makeReParenthesesInlineTextChars(""), 43 | reOutsideParenthesesTextChars: makeReOutsideParenthesesTextChars(""), 44 | rePeriodSentenceTextChars: makeRePeriodSentenceTextChars(""), 45 | ...options, 46 | }, 47 | state, 48 | baseOffset, 49 | }; 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /app/src/components/LawView/ReplaceHTMLFigRun.tsx: -------------------------------------------------------------------------------- 1 | import React, { } from "react"; 2 | import styled from "styled-components"; 3 | import type { HTMLFigData, WrapperComponentProps } from "lawtext/dist/src/renderer/common/html"; 4 | import type * as std from "lawtext/dist/src/law/std"; 5 | import { NotImplementedError } from "lawtext/dist/src/util"; 6 | import { useObserved } from "../useObserved"; 7 | 8 | 9 | export const ReplaceHTMLFigRun: React.FC = props => { 10 | const { childProps, ChildComponent } = props; 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | const el = (childProps as any).el as std.Fig; 13 | 14 | if (el.children.length > 0) { 15 | throw new NotImplementedError(el.outerXML()); 16 | } 17 | 18 | const { getFigData } = childProps.htmlOptions; 19 | 20 | const figData = React.useMemo(() => { 21 | if (getFigData) { 22 | return getFigData(el.attr.src); 23 | } 24 | return null; 25 | }, [el.attr.src, getFigData]); 26 | 27 | if (figData && figData.type.includes("pdf")) { 28 | return ( 29 | 30 | ); 31 | } else { 32 | return ( 33 | 34 | ); 35 | } 36 | }; 37 | 38 | export default ReplaceHTMLFigRun; 39 | 40 | const FigIframeDummy = styled.div` 41 | display: inline-block; 42 | width: 100%; 43 | height: 80vh; 44 | border: 1px solid gray; 45 | `; 46 | 47 | export const PDFRun: React.FC<{figData: HTMLFigData, src: string}> = props => { 48 | const { figData, src } = props; 49 | 50 | const { observed, observedRef } = useObserved(); 51 | 52 | return ( 53 | 54 | {observed 55 | ? ( 56 |