Author SHA1 Message Date
erikandClaude Sonnet 5 11eabe42ae Protokoll-Import: KI-Erkennung (Gruppe, Teilnehmer, Status, Mehrfachpunkte), Ladeanzeige, Erstellungsdatum je Punkt
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 10:54:43 +02:00
GitButler 6f18848f86 GitButler Workspace Commit
This is placeholder commit and will be replaced by a merge of your virtual branches.

Due to GitButler managing multiple virtual branches, you cannot switch back and
forth between git branches and virtual branches easily. 

If you switch to another branch, GitButler will need to be reinitialized.
If you commit on this branch, GitButler will throw it away.

For more information about what we're doing here, check out our docs:
https://docs.gitbutler.com/features/branch-management/integration-branch
2026-10-01 06:47:37 +02:00
erik 9d361013ba test 2026-10-01 06:47:37 +02:00
erik 5b20adee48 All new
alle new
2026-10-01 06:46:51 +02:00
erik 4fc5acfebb Claude change 2026-09-14 18:10:49 +02:00
erik 1c176ea621 DB Import und Export sowie Backup und kleinere Fehlerbeseitigung 2026-08-18 09:16:59 +02:00
erik 91cc898ec7 Legende Projektkürzel im dark mode jetzt auch dunkel 2026-08-14 15:15:22 +02:00
erik 4e083bee73 Unnötige Datein gelöscht. DB normalisiert 2026-08-14 15:03:24 +02:00
1865 changed files with 469582 additions and 9396 deletions

No files matched your search

Vendored
BIN
View File
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
# SQLite-Datenbank (Laufzeitartefakt in sessions/)
sessions/*.db
sessions/*.db-wal
sessions/*.db-shm
# macOS-Artefakte
.DS_Store
sessions/*.txt
+1
View File
@@ -0,0 +1 @@
omit=optional
-198
View File
@@ -1,198 +0,0 @@
# AGENTS.md
## Overview
Single-page app for tracking engineering projects across weekly sessions
("Sitzungen"). Vanilla JS/HTML/CSS frontend + Node.js/Express server. No build
step, no bundler, no tests. Data is persisted as semicolon-separated CSVs
under `sessions/`.
Two HTML entrypoints, both served statically by Express:
- `Test.html` — the main app (tabs: Projektboard, Auslastung, Projekte,
Projektbeteiligte). Served at `/`.
- `project-plan.html` — per-project Gantt/TODO planning view, opened in a new
tab via `openProjectPlan()` in `js/gantt.js`. Loads the shared helper modules
(`globals`, `utils`, `ui`, `date-picker`, `csv`, `server`, `gantt`) plus the
plan modules under `js/plan/` — NOT the full main module set.
All UI strings/comments/CSV content are German. Preserve German in
user-facing text.
## File structure
```
.
├── server.js # Express server + CSV API
├── package.json # deps: express, cors only
├── docker-compose.yml # at repo ROOT; build context ., dockerfile Docker/Dockerfile
├── Docker/Dockerfile # node:20-alpine
├── .dockerignore
├── .vscode/extensions.json
├── public/
│ ├── Test.html # main app (served at /)
│ ├── js/ # shared modules (classic scripts, global fns)
│ │ ├── globals.js # Konstanten (LPHS, ROLES, LPH-Farben, DEPARTMENTS) + db/currentDepartment
│ │ ├── utils.js # esc, uid, Datum-/Differenz-Helfer, personIdForName, peopleByRole, assignKey, weekSortKey-Quelle
│ │ ├── ui.js # LocalStorage-UI-State, Header-Collapse, Theme
│ │ ├── date-picker.js # Datums-/Bereichspicker-Overlays
│ │ ├── csv.js # CSV-Parser/Serializer + weekSortKey, filenameForWeek
│ │ ├── server.js # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl
│ │ ├── store.js # save/syncAllToServer, switchDepartment, load, seed
│ │ ├── weeks.js # Wochen-Auswahl, -Erzeugung, -Klonen, Auto-Lock, -Löschung
│ │ ├── gantt.js # GANTT-Diagramm (buildGanttHtml, openProjectPlan), geshared
│ │ ├── showmode.js # Show-Mode (Präsentationsmodus)
│ │ ├── tabs-auth.js # showTab, confirmPassword (SHA256-Passwortschutz)
│ │ ├── pdf.js # PDF-Export (pdfmake, A3 quer)
│ │ └── init.js # renderAll/initUiState + Seiteneinstieg (nur Test.html)
│ │ ├── board/ # tab-specific: Projektboard
│ │ │ └── board.js # Projektboard-Rendering, Status-Editor, Anwesenheit, Zuordnung
│ │ ├── people/ # tab-specific: Projektbeteiligte
│ │ │ └── people.js # Personen-Tab-Rendering + Drag-and-Drop + Abwesenheiten
│ │ ├── projects/ # tab-specific: Projekte
│ │ │ └── projects.js # Projekte-Tab-Rendering + Drag-and-Drop
│ │ ├── load/ # tab-specific: Auslastung
│ │ │ └── load.js # Auslastungs-Tab, LP-Modal, Neues-Projekt-Modal
│ ├── project-plan.html # Gantt/TODO planning view
│ ├── js/plan/ # planning modules (nur von project-plan.html geladen)
│ │ ├── plan-state.js # PlanApp-Zustand, Datenladepath, savePlan, Toasts
│ │ ├── plan-build-gantt.js # buildGanttHtml-Erweiterung (TODO- und Meilensteinzeilen)
│ │ ├── plan-gantt.js # renderPlanGantt, Abhängigkeitslinien, Zoom, Ressourcen-Load
│ │ ├── plan-drag.js # GANTT-Balken-Drag/Resize + TODO-/Meilenstein-Zeilen-Drag
│ │ ├── plan-modals.js # TODO-, Meilenstein- und Abwesenheits-Modals
│ │ └── plan-schedule.js # Kritischer Pfad, Auto-Schedule, Mail-Link
│ ├── styles.css
│ └── dein_bild.ico
└── sessions/ # data; bind-mounted into Docker container
├── ELT/ HLS/ ARCH/ # one folder per department
```
## Running
```bash
npm install
npm start # http://localhost:3003, or PORT=3004 if occupied
docker compose up --build # Docker variant (compose file is at repo root)
```
- PORT env var overrides default 3003 in `server.js`.
- Frontend must be served over HTTP: `baseUrl()` in `js/server.js` uses
`window.location.origin` or falls back to `http://localhost:3003` when opened
via `file://` (then only seed/demo data loads).
- Check `/health` returns `{ok:true}` after starting.
## Server API (`server.js`)
- `GET /` → `Test.html`; `GET /health`.
- Deep links per department + tab: `GET /<Dept>/<Tab>` → `Test.html`, e.g.
`/HLS/Auslastung` or `/ELT/Projektübersicht`. Tab slugs (`TAB_SLUGS` in
`js/globals.js`) are `Projektübersicht|Auslastung|Projekte|Projektbeteiligte`;
anything else returns 404. Exception: `GET /<Dept>/Projektplan(/<ProjName>)?`
(`PLAN_SLUG`) serves `project-plan.html`, the GANTT view, with
`weekLabel|projNr` as query params (the project name is a readable path
segment only; identification happens via weekLabel + projNr). The frontend
reads/writes these routes via `applyUrlRoute()`/`updateRouteUrl()` in
`js/ui.js` and all static assets in the HTML use **absolute** paths
(`/js/...`, `/styles.css`) so they resolve on nested routes.
- `GET /load-all?department=` → `{sessions:[{filename,content}], people}` with
`_people.csv` content in `people`; sessions sorted by filename.
- `POST /save-csv {filename, csv, department}` → writes UTF-8 BOM CSV.
- `POST /delete-csv {filename, department}`.
- Valid departments hard-coded as `ELT|HLS|ARCH` in `server.js`
(`VALID_DEPARTMENTS`); anything else silently falls back to `ELT`. Same set in
`DEPARTMENTS` in `js/globals.js`.
- `safeName()` strictly validates filenames (path traversal protection);
static assets get `Cache-Control: no-store`.
- JSON body limit is 2 MB.
## CSV format
- Delimiter `;`, UTF-8 with BOM. Hand-editing is possible but risky; the parser
reconstructs objects from the fixed column layout.
- Week files `<Label>.csv` (e.g. `KW_30___2026.csv` — `filenameForWeek()`
replaces non-word chars with `_`). Row layout is positional:
`WOCHE` (label, date, locked flag) → `PERSONEN` (`name|role|fullName` per
column) → header row → `ANWESENHEIT` → one row per project.
- `_people.csv` per department: `NAME;Rollen;Voller Name;E-Mail;Abwesenheiten`
(absences as JSON). People are global per department; new people are copied
into the current week. Historical weeks keep their own person copies, so
deleting a global person leaves old sessions unaffected no matter what.
- Don't change the column layout without updating both `weekToCsv()` and
`parseSessionCsv()` (`js/csv.js`), which must stay inverse of each other.
## Frontend architecture
- The frontend is split into small classic non-module `<script>` files under
`public/js/`; **all functions are globals on `window`**. Two HTML files include
them via `<script src>` tags; load order in the HTML defines dependencies,
since there is no bundler:
- `Test.html` loads `globals → utils → ui → date-picker → csv → server →
store → weeks` (shared modules), then the tab-specific folders `board →
people → projects → load`, finally `gantt → showmode → tabs-auth → pdf →
init`. See the exact list in `Test.html`.
- `project-plan.html` loads only `globals → utils → ui → date-picker → csv →
server → gantt` plus the `js/plan/*` modules. It must NOT load modules that
touch main-only page elements (`store`, `weeks`, `board`, …, `init`).
- Shared state object `window.PlanApp` (`js/plan/plan-state.js`) holds all
project-plan page state (week, project, zoom level, drag/`modal`-state),
`db`/`currentDepartment` live in `js/globals.js`.
- State: `let db = { weeks, currentWeek, globalPeople }` (in-memory only).
- Project IDs are regenerated on every CSV load, so projects are addressed
cross-page by `department` + week `label` + `projNr` (+ readable projName path
segment) via URL params: `openProjectPlan()` builds
`/HLS/Projektplan/<ProjName>?weekLabel=…&projNr=…`, `findProjectForPlan`
resolves them.
- Person IDs are stable across reloads via `personIdForName(name)` →
`p_<lowercased alnum>`.
- `save()` debounces 500 ms then `syncAllToServer()` writes **all** weeks plus
`_people.csv` — not just the changed week. Exception: `js/plan/plan-state.js`
`savePlan()` bypasses `save()` and writes only the current week's CSV directly
via `sendCsvToServer()`.
- `switchDepartment()` cancels a pending save debounce before switching, and
`load()` discards stale `/load-all` responses after a department switch
(frozen `requestedDepartment`), so late responses can never render or persist
data under the wrong department.
- Person drag-and-drop in the people tab: drag handle (`.drag-handle`) binds
`ondragstart`/`ondragend`, the `.people-sortable` container binds
`ondragover`/`ondrop`; insertion is computed by `getDragAfterElement(container,
y, selector)` (`js/projects/projects.js`, used with `.person-card` here and
`.person-list-item` in the projects tab). `reorderPeople()` updates both
`db.globalPeople` and the current week's person copy, then `save()`. No drag
handles are rendered on locked weeks.
- Weeks are sorted by calendar week extracted from the label (`KW XX / YYYY`)
via `weekSortKey()`, not alphabetically/by filename.
- `applyAutoLock()` locks every week except the chronologically last one after
load/clone/new-week; `localStorage` UI state is not restored for locked tabs.
- `localStorage` keys: `projuebersicht_v1` (department only — tab is never
restored from storage; at startup the tab comes from the deep-link URL if
present, otherwise `board`), `projuebersicht-theme`, header-collapse key. Do
**not** store data in `localStorage`; it is UI state only.
- Deep-link URLs (`/<Dept>/<Tab>`) are shared via the address bar:
`updateRouteUrl()` rewrites the path via `history.replaceState` on tab and
department switches; protected tabs keep the URL but open the password modal.
## Auth / security quirk
- The `projects` and `people` tabs, week deletion, and week unlock are gated by
a password modal comparing `CryptoJS.SHA256(input)` to
`PROTECTED_PASSWORD_HASH` (`js/globals.js`, modal in `js/tabs-auth.js`).
Client-side only — not real security. CryptoJS and pdfmake (PDF export) are
loaded from `cdnjs.cloudflare.com` in `Test.html`, so those features need
internet access and won't work offline. `project-plan.html` loads neither.
- User-provided status HTML is sanitized on render via a small whitelist
(`sanitizeStatusHtml` in `js/board/board.js`) to mitigate XSS.
## Docker
- `docker compose up --build` from the repo root. Compose maps `3003:3003`,
bind-mounts `./sessions:/app/sessions`, `restart: unless-stopped`.
- Dockerfile: `node:20-alpine`, `npm ci --production`, runs as non-root `node`,
HEALTHCHECK on `/health`. `.dockerignore` excludes `Docker/*` (except
`Docker/Dockerfile`), `node_modules/`, `.git`, `npm-debug.log`.
## Conventions & verification
- Git repo; commits are German (e.g. "PDF Export von A4 auf A3 …"). Remote:
`http://192.168.3.187:3000/erik/projektuebersicht.git`.
- No linter, formatter, typecheck, or test suite. Manual verification only:
start server, open `http://localhost:3003`, exercises the relevant tab,
confirm CSV changes in `sessions/`.
+2846
View File
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -1,5 +1,7 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM node:20-alpine # node:22-alpine, weil node:sqlite (SQLite-Persistenz in server.js) erst ab
# Node 22.13 (ohne Flag) stabil verfügbar ist.
FROM node:22-alpine
WORKDIR /app WORKDIR /app
@@ -11,7 +13,7 @@ RUN npm ci --production
COPY server.js ./ COPY server.js ./
COPY public ./public COPY public ./public
# Sessions-Ordner fuer die CSV-Daten sicherstellen # Sessions-Ordner fuer die SQLite-Datenbank sicherstellen
RUN mkdir -p /app/sessions && chown -R node:node /app RUN mkdir -p /app/sessions && chown -R node:node /app
USER node USER node
+65 -56
View File
@@ -1,12 +1,12 @@
# Projektübersicht — README # Projektübersicht — README
## Übersicht ## Übersicht
Dieses Projekt ist eine lokale Single-Page-Webanwendung zur Verwaltung elektrotechnischer Projekte und Sitzungsübersichten. Die Anwendung besteht aus: Dieses Projekt ist eine lokale Weboberfläche zur Verwaltung von TGA-Projekten (Elektro, HLS, Architektur) über wöchentliche Sitzungen. Die Anwendung besteht aus:
- einem Frontend in `Test.html`, `styles.css` und den JS-Modulen unter `public/js/` - einem Frontend mit `Test.html` als der einen Anwendungsseite — sie enthält alle Bereiche einschließlich des Projektbereichs eines einzelnen Projekts (Terminplan/GANTT, Aufgaben, Dateien, Einstellungen) — dazu `styles.css`, `plan-styles.css` und den JS-Modulen unter `public/js/`
- einem einfachen Node.js-Server in `server.js` - einem einfachen Node.js-/Express-Server in `server.js`
- einem Datenspeicher in CSV-Dateien unter `sessions/` - einem Datenspeicher in einer SQLite-Datenbank (`node:sqlite`, benötigt Node ≥ 22.13) unter `sessions/projektuebersicht.db`
- einem Docker-Setup unter `Docker/` - einem Docker-Setup (`Docker/Dockerfile` + `docker-compose.yml` im Projektstamm)
## Datei- und Komponentenübersicht ## Datei- und Komponentenübersicht
@@ -14,14 +14,19 @@ Dieses Projekt ist eine lokale Single-Page-Webanwendung zur Verwaltung elektrote
- `server.js` - `server.js`
- Node.js/Express-Server - Node.js/Express-Server
- dient als API für das Frontend - dient als API für das Frontend
- stellt statische Dateien (`Test.html`, die Module unter `js/`, `styles.css`, etc.) bereit - stellt statische Dateien (`Test.html`, die Module unter `js/`, `styles.css`, `plan-styles.css`, etc.) bereit; statische Assets werden mit `Cache-Control: no-store` ausgeliefert
- implementiert Endpunkte: - implementiert Endpunkte:
- `GET /` → liefert `Test.html` - `GET /` → liefert `Test.html`
- `GET /load-all` → lädt alle CSV-Dateien aus dem aktiven `sessions/<DEPARTMENT>/`-Ordner inkl. `_people.csv` - `GET /load-all?department=…` → lädt alle Sitzungen plus globale Personenliste einer Abteilung
- `POST /save-csv` → speichert eine CSV-Datei (Dateiname wird validiert) - `POST /save-week {department, week, baseUpdatedAt}` → speichert/aktualisiert eine Sitzung; mit `baseUpdatedAt` (die vom Client gehaltene Revision = `updatedAt` aus `load-all`) erkennt der Server veraltete Speicherungen und antwortet dann mit HTTP 409 `{ok:false, conflict:true, …}`; auch gelöschte Sitzungen werden mit nicht-leerer Basis nicht wiederbelebt (409). Erfolgreiche Saves antworten `{ok:true, updatedAt}` (neue Revision)
- `POST /delete-csv` → löscht eine CSV-Datei - `POST /save-people {department, people}` → ersetzt die globale Personenliste einer Abteilung
- `GET /health` → Gesundheitscheck - `POST /delete-week {department, label}` → löscht eine Sitzung
- `GET /health` → Gesundheitscheck (`{ok:true}`)
- Deep Links je Abteilung + Tab, z. B. `GET /HLS/Auslastung` oder `GET /ELT/Projektübersicht` → liefert `Test.html`; gültige Tab-Slugs sind `Projektübersicht | Auslastung | Projekte | Projektbeteiligte`, alles andere ergibt 404
- `GET /Projektplan(/<ProjName>)?` → liefert ebenfalls `Test.html`; der Projektbereich ist seit dem Umbau kein eigenes Dokument mehr, sondern ein Bereich dieser Seite, den `applyUrlRoute()` (`public/js/ui.js`) aus der Adresse auswählt. Die Identifikation erfolgt über die Query-Parameter `weekLabel` und `projNr`, der Projektname ist nur ein lesbares Pfad-Segment. Alte Links auf `/project-plan.html` werden per 301 auf dieses Schema umgeleitet
- validiert Abteilungen gegen `ELT | HLS | ARCH` (unbekannte fallen stillschweigend auf `ELT` zurück)
- legt das `sessions`-Verzeichnis beim Start an, falls es nicht existiert - legt das `sessions`-Verzeichnis beim Start an, falls es nicht existiert
- JSON-Body-Limit von 2 MB
- beendet sich mit aussagekräftiger Fehlermeldung, wenn der Port belegt ist - beendet sich mit aussagekräftiger Fehlermeldung, wenn der Port belegt ist
- `package.json` - `package.json`
@@ -32,54 +37,66 @@ Dieses Projekt ist eine lokale Single-Page-Webanwendung zur Verwaltung elektrote
- wird für reproduzierbare Docker-Builds verwendet (`npm ci`) - wird für reproduzierbare Docker-Builds verwendet (`npm ci`)
- `public/js/` (Module) - `public/js/` (Module)
- Die Client-Logik ist in kleine klassische Script-Dateien aufgeteilt (alle Funktionen global auf `window`): - Die Client-Logik ist in kleine klassische Script-Dateien aufgeteilt (alle Funktionen global auf `window`, kein Bundler):
`globals.js`, `utils.js`, `ui.js`, `date-picker.js`, `csv.js`, `server.js`, `store.js`, `weeks.js`, - gemeinsame Helfer: `globals.js`, `utils.js`, `ui.js`, `date-picker.js`, `server.js`, `store.js`, `weeks.js`, `gantt.js`, `showmode.js`, `tabs-auth.js`, `pdf.js`, `init.js`
`board.js`, `people.js`, `projects.js`, `load.js`, `gantt.js`, `showmode.js`, `tabs-auth.js`, `pdf.js`, - tab-spezifisch in eigenen Ordnern: `board/board.js` (Projektboard), `people/people.js` (Projektbeteiligte), `projects/projects.js` (Projekte), `load/load.js` (Auslastung)
`init.js` sowie die Plan-Module unter `js/plan/` (`plan-state`, `plan-build-gantt`, `plan-gantt`, - Planungs-Module unter `js/plan/` (`plan-state`, `plan-build-gantt`, `plan-gantt`, `plan-drag`, `plan-modals`, `plan-schedule`)
`plan-drag`, `plan-modals`, `plan-schedule`).
- rendert das Projektboard, die Projekt- und Personenverwaltung sowie die Auslastungsansicht - rendert das Projektboard, die Projekt- und Personenverwaltung sowie die Auslastungsansicht
- lädt Daten über den Server und speichert Änderungen zurück als CSV - lädt Daten über den Server und speichert Änderungen als JSON-Objekte in die SQLite-Datenbank zurück
- verwendet `currentDepartment` zur Trennung von Abteilungen (`ELT`, `HLS`, `ARCH`) - verwendet `currentDepartment` zur Trennung von Abteilungen (`ELT`, `HLS`, `ARCH`)
- speichert: - speichert:
- `sessions/<DEPT>/<WEEK>.csv` - alle Sitzungen und die globale Personenliste pro Abteilung in `sessions/projektuebersicht.db`
- `sessions/<DEPT>/_people.csv` (globale Personenliste pro Abteilung)
- sortiert Sitzungen nach Kalenderwoche, nicht alphabetisch - sortiert Sitzungen nach Kalenderwoche, nicht alphabetisch
- sperrt historische Sitzungen automatisch (bis auf die chronologisch letzte) - sperrt historische Sitzungen automatisch (bis auf die chronologisch letzte)
- Personen sind global pro Abteilung verwaltet; historische Sitzungen behalten ihre lokalen Personenkopien - Personen sind global pro Abteilung verwaltet; historische Sitzungen behalten ihre lokalen Personenkopien
- speichert keine Daten in `localStorage` (dort liegt nur UI-Zustand wie Abteilung, Theme und Header-Collapse)
- `Test.html` - `Test.html`
- die Benutzeroberfläche der Anwendung - die Haupt-Benutzeroberfläche der Anwendung (Tabs: Projektboard, Auslastung, Projekte, Projektbeteiligte)
- lädt `styles.css` und die JS-Module aus `js/` in fester Reihenfolge (siehe `AGENTS.md`) - lädt `styles.css` und die JS-Module aus `js/` in fester Reihenfolge (siehe `AGENTS.md`)
- enthält die Abteilungsauswahl, Sitzungsnavigation, Tab-Navigation und das Passwort-Modal - enthält die Abteilungsauswahl, Sitzungsnavigation, Tab-Navigation, das Passwort-Modal sowie PDF-Export
- lädt CryptoJS (Passwortprüfung) und pdfmake (PDF-Export) von `cdnjs.cloudflare.com` — diese Funktionen benötigen daher Internetzugang
- `project-plan.html` - Projektbereich (Bereich `#plan` in `Test.html`, Module unter `js/plan/`)
- die Planungsansicht (GANTT/TODOs/Meilensteine) pro Projekt - der eigene Bereich EINES Projekts mit Dashboard, Terminplan (GANTT/TODOs/Meilensteine), Aufgaben, Dateien und Einstellungen
- öffnet sich in einem neuen Tab über `openProjectPlan()` (`js/gantt.js`) - wird über `openProjectPlan()` (`js/gantt.js`) geöffnet — ein gewöhnlicher Bereichswechsel über `showTab('plan')`, genau wie vom Dashboard ins Projektboard; Menüband und Seitenleiste bleiben stehen
- lädt nur die gemeinsamen Helfermodule plus `js/plan/*` - war bis zum Umbau ein eigenes Dokument `project-plan.html` mit nachgebauter, abgespeckter Seitenleiste; die Datei ist entfallen, ihre Stile liegen jetzt in `plan-styles.css`
- `plan-styles.css`
- die Stile des Projektbereichs (früher ein `<style>`-Block in `project-plan.html`)
- Bereich „Einstellungen" (`js/settings/settings.js`)
- gehört dem angemeldeten Konto selbst: Stammdaten zur Ansicht und der Passwortwechsel (`POST /change-password`). Der Dialog erscheint auch erzwungen direkt nach der Anmeldung, solange das Konto `must_change_password` trägt
- Bereich „Admin" (`js/admin/admin.js`, nur für Administratoren)
- gegliedert in einzelne Abschnitte, zwischen denen die aufklappbare Gruppe „Admin" in der Seitenleiste umschaltet; sichtbar ist immer genau einer, der gewählte steht im URL-Fragment (z. B. `/Admin#auslastung`) und ist damit verlinkbar
- Abschnitte: „Benutzerverwaltung" (Konten, Rollen, Wochenstunden, Abwesenheiten — bis zum Umbau die eigene Seite `admin-users.html`, alte Links werden per 301 umgeleitet), „Honorar" (Stundensatz, Anteile je Leistungsphase), „Auslastung" (Wochenstunden einer vollen Stelle, Ampel-Schwellen, Balkenskala, Aufwand je Leistungsphase und Rolle), „Termine & Sitzungen" (Arbeitsstunden je Werktag, automatische Sperre), „Feiertage" (die gesetzlichen Feiertage eines Jahres werden berechnet — feste Termine plus alles, was am Osterdatum hängt — und auf Knopfdruck übernommen; kein Netzdienst, kein Internetzugang nötig), „Aufgaben-Vorlagen", „Betrieb & Sicherheit" (aufbewahrte Backups, Gültigkeit einer Anmeldung, Anmeldebremse) und „Datenbank & Sitzungen" (DB Backup, DB Import, Sitzung löschen)
- all diese Werte waren vorher Konstanten im Quelltext; gelesen werden sie über `GET /app-settings` (jedes Konto — das Frontend rechnet damit), geschrieben über `PUT /app-settings` (nur Administratoren, serverseitig über `requireRole('admin')` abgesichert)
- die vertraglichen Wochenstunden je Person stehen am Benutzerkonto (Benutzerverwaltung) und sind die Bezugsgröße der Auslastungs-Ampel: Eine halbe Stelle gilt schon bei halber Last als voll ausgelastet
- `styles.css` - `styles.css`
- Layout und Styling der Oberfläche - Layout und Styling der Oberfläche
### Docker-bezogene Dateien ### Docker-bezogene Dateien
- `Docker/Dockerfile` - `Docker/Dockerfile`
- Basis-Image: `node:20-alpine` - Basis-Image: `node:22-alpine` (nötig, da `node:sqlite` erst ab Node 22.13 ohne Flag stabil verfügbar ist)
- kopiert `package.json` und `package-lock.json`, installiert Produktionsabhängigkeiten mit `npm ci` - kopiert `package.json` und `package-lock.json`, installiert Produktionsabhängigkeiten mit `npm ci`
- kopiert den Rest des Projekts in `/app` - kopiert `server.js` und `public/` nach `/app`
- legt sicherheitshalber `/app/sessions` an - legt sicherheitshalber `/app/sessions` an
- läuft als nicht-root-User `node` - läuft als nicht-root-User `node`
- enthält einen `HEALTHCHECK` auf `/health` - enthält einen `HEALTHCHECK` auf `/health`
- startet `node server.js` - startet `node server.js`
- `docker-compose.yml` - `docker-compose.yml` (im Projektstamm)
- definiert den Service `projektuebersicht` - definiert den Service `projektuebersicht`
- baut das Image aus dem Projektstamm mit `Docker/Dockerfile` - baut das Image mit Build-Kontext `.` und `Docker/Dockerfile`
- mappt Port `3003:3003` - mappt Port `3003:3003`
- bindet das Host-Verzeichnis `./sessions` in den Container nach `/app/sessions` - bindet das Host-Verzeichnis `./sessions` in den Container nach `/app/sessions`
- setzt `restart: unless-stopped` - setzt `restart: unless-stopped`
- definiert einen `healthcheck` - definiert einen `healthcheck`
- `.dockerignore` - `.dockerignore`
- schließt unnötige Dateien aus dem Docker-Build aus: `Docker/`, `node_modules/`, `npm-debug.log`, `.git`, `.gitignore` - schließt unnötige Dateien aus dem Docker-Build aus: `node_modules/`, `npm-debug.log`, `.git`, `.gitignore`, `Docker/*` (außer `Docker/Dockerfile`)
### Sonstige Dateien ### Sonstige Dateien
- `dein_bild.ico` - `dein_bild.ico`
@@ -88,34 +105,25 @@ Dieses Projekt ist eine lokale Single-Page-Webanwendung zur Verwaltung elektrote
- `AGENTS.md` - `AGENTS.md`
- Projektnotizen / Dokumentation, aber nicht Teil des Anwendungslaufs - Projektnotizen / Dokumentation, aber nicht Teil des Anwendungslaufs
## Speicherorte und Datenfluss ## Persistenz und Datenfluss
### Speicherung ### Speicherung
- Alle persistierten Daten werden als CSV-Dateien in `sessions/` gespeichert. - Alle persistierten Daten werden in einer SQLite-Datenbank (`node:sqlite`, `DatabaseSync`, WAL-Modus, `PRAGMA foreign_keys = ON`) unter `sessions/projektuebersicht.db` gespeichert.
- Innerhalb von `sessions/` existieren Abteilungsordner: - Das Schema ist vollständig relational (19 Tabellen): `weeks`, `week_people` (+ `week_people_roles`), `attendance`, `projects`, `project_lph`, `project_assign`, `project_aufwand`, `project_aktuelle_lp`, `project_plan`, `plan_people`, `plan_todos` (+ `plan_todo_assignees`, `plan_todo_deps`), `plan_milestones` (+ `plan_milestone_deps`), `people` (+ `person_roles`, `person_absences`). Projekt-/Personen-IDs bleiben dadurch über Neustarts stabil.
- `sessions/ELT/` - Der Datenbankpfad ist über die Umgebungsvariable `DB_PATH` übersteuerbar (für Tests/Backups), Standard ist `sessions/projektuebersicht.db`.
- `sessions/HLS/`
- `sessions/ARCH/`
- Jede Abteilung enthält mindestens:
- `_people.csv` → globale Personenliste mit Rollen und vollständigem Namen
- `KW_XX___YYYY.csv` oder vergleichbare Sitzungsdateien → Sitzungsdaten mit Projekten, Zuordnungen und Anwesenheit
### Wo wird der Server erstellt? ### Server-API
- Der Server wird in `server.js` erstellt. - Der Server wird in `server.js` erstellt und startet auf Port `3003` oder dem Wert von `process.env.PORT`.
- `server.js` startet einen Express-Server auf Port `3003` oder dem Wert von `process.env.PORT`. - Das Frontend ruft den Server über `window.location.origin` an (Fallback `http://localhost:3003` bei `file://`-Öffnung).
- Das Frontend ruft diesen Server über `window.location.origin` an.
### Wo werden die Dateien gespeichert? ### Wo werden die Daten gespeichert?
- Das Frontend schreibt über den Server in das Dateisystem. - Das Frontend schreibt über den Server in die SQLite-Datenbank.
- In `js/store.js` ruft die Anwendung beim Speichern `/save-csv` auf. - In `js/store.js` speichert die Anwendung nur **geänderte** Sitzungen (Dirty-Tracking über `markWeekDirty()`/`markPeopleDirty()`) via `/save-week` (Sitzungen) bzw. `/save-people` (globale Personen); ein In-Flight-Guard verhindert Doppel-Speicherungen. Jede Sitzung führt einen Revisions-Zeitstempel (`updated_at`, übertragen als `updatedAt`); speichert ein Client mit veralteter Revision (`baseUpdatedAt`), antwortet der Server mit HTTP 409 und der Client lädt nur diese eine Sitzung neu (andere ungespeicherte Änderungen bleiben erhalten).
- Der Server legt CSV-Dateien unter `sessions/<DEPARTMENT>/` ab. - Der Server zerlegt die JSON-Objekte in die relationalen Tabellen (`serializeWeek()`/`loadWeeks()`) in einer Transaktion pro Sitzung. Beim Start migriert `migrateLegacySchema()` idempotent ein altes Single-JSON-Dokument-Schema in das relationale Schema; anschließend ergänzt `migrateWeeksUpdatedAt()` die `updated_at`-Spalte der `weeks`-Tabelle (idempotent). `PRAGMA busy_timeout = 5000` verhindert Fehler bei parallelen Schreibzugriffen.
- Beispiel:
- `sessions/ELT/KW_32___2026.csv`
- `sessions/ELT/_people.csv`
## Betrieb ## Betrieb
### Lokal mit Node.js ### Lokal mit Node.js (Node ≥ 22.13)
```bash ```bash
npm install npm install
npm start npm start
@@ -131,7 +139,7 @@ PORT=3004 npm start
Im Projektstamm ausführen: Im Projektstamm ausführen:
```bash ```bash
docker compose -f Docker/docker-compose.yml up --build docker compose up --build
``` ```
Danach ist die Anwendung erreichbar unter: Danach ist die Anwendung erreichbar unter:
@@ -141,8 +149,9 @@ http://localhost:3003
``` ```
## Wichtige Hinweise ## Wichtige Hinweise
- Der Server benötigt Lese-/Schreibzugriff auf `sessions/`. - Der Server benötigt Lese-/Schreibzugriff auf `sessions/` (für die SQLite-Datenbank und das Docker-Bind-Mount).
- Die Docker-Compose-Konfiguration mountet `./sessions` in den Container. Achte darauf, dass der relative Pfad im Kontext korrekt ist, wenn du `docker compose` aus einem anderen Verzeichnis startest. - Die Docker-Compose-Konfiguration liegt im Projektstamm und mountet `./sessions` in den Container. Achte darauf, dass der relative Pfad im Kontext korrekt ist, wenn du `docker compose` aus einem anderen Verzeichnis startest.
- `js/store.js` und `js/server.js` arbeiten mit Abteilungsparametern und speichern Daten abteilungsweise in separaten Unterordnern. - `js/store.js` und `js/server.js` arbeiten mit Abteilungsparametern und speichern Daten abteilungsweise getrennt.
- Die Passwortfunktion ist nur ein einfacher Client-Mechanismus im Frontend und kein echter Sicherheitsmechanismus. - Die Passwortfunktion (Tabs „Projekte“/„Projektbeteiligte“, Sitzungslöschung, Entsperren) ist nur ein einfacher Client-Mechanismus im Frontend (SHA256-Vergleich) und kein echter Sicherheitsmechanismus.
- Globale Personen werden in `_people.csv` verwaltet. Wird eine Person global gelöscht, bleibt sie in bereits existierenden Sitzungen sichtbar. - Globale Personen werden pro Abteilung in der Datenbank verwaltet. Wird eine Person global gelöscht, bleibt sie in bereits existierenden Sitzungen sichtbar (Wochen-Personenkopien).
- CryptoJS und pdfmake werden von `cdnjs.cloudflare.com` geladen — PDF-Export und Passwortprüfung funktionieren ohne Internet nicht.
+6
View File
@@ -8,6 +8,12 @@ services:
- "3003:3003" - "3003:3003"
volumes: volumes:
- ./sessions:/app/sessions - ./sessions:/app/sessions
environment:
# Ohne TZ läuft der Container auf UTC. Der Server vergleicht Termine
# mit dem heutigen Datum (heuteIsoLokal() in server.js) — in deutscher
# Sommerzeit gälte damit zwischen 00:00 und 02:00 Ortszeit noch der
# Vortag, und Termine von heute erschienen als überfällig.
TZ: Europe/Berlin
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3003/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"] test: ["CMD", "node", "-e", "require('http').get('http://localhost:3003/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"]
Generated Vendored
BIN
View File
Binary file not shown.
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../mammoth/bin/mammoth
+252 -15
View File
@@ -4,6 +4,15 @@
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"node_modules/@xmldom/xmldom": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
"integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -17,16 +26,45 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.6", "version": "1.20.8",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",
"integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "~3.1.2", "bytes": "~3.1.2",
@@ -37,7 +75,7 @@
"http-errors": "~2.0.1", "http-errors": "~2.0.1",
"iconv-lite": "~0.4.24", "iconv-lite": "~0.4.24",
"on-finished": "~2.4.1", "on-finished": "~2.4.1",
"qs": "~6.15.1", "qs": "~6.16.0",
"raw-body": "~2.5.3", "raw-body": "~2.5.3",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
@@ -121,6 +159,12 @@
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cors": { "node_modules/cors": {
"version": "2.8.6", "version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
@@ -166,6 +210,21 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/dingbat-to-unicode": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.2.tgz",
"integrity": "sha512-R0bRerYzy/EZP6QzD3Hkl6JjUDA1Mnn+pd928w0nWjPzihnbAR6dcjGJGiRyznj5E4S1duEKzWrTqw02DPcdzg==",
"license": "BSD-2-Clause"
},
"node_modules/duck": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
"integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
"license": "BSD",
"dependencies": {
"underscore": "^1.13.1"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -241,9 +300,9 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.22.2", "version": "4.22.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz",
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
@@ -265,9 +324,9 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "~2.4.1", "on-finished": "~2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12", "path-to-regexp": "~0.1.13",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "~6.15.1", "qs": "~6.16.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "~0.19.0", "send": "~0.19.0",
@@ -436,6 +495,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -451,6 +516,66 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.2",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz",
"integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lop": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
"integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==",
"license": "BSD-2-Clause",
"dependencies": {
"duck": "^0.1.12",
"option": "~0.2.1",
"underscore": "^1.13.1"
}
},
"node_modules/mammoth": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.13.0.tgz",
"integrity": "sha512-2cawcYFcP7ISfXbBcL9lmVgTNWo88v/52ND1gnywHtVT/pX0//puPnf0iMPV9ZgmuvNOE+zkIhDgZ9M2c2dDDg==",
"license": "BSD-2-Clause",
"dependencies": {
"@xmldom/xmldom": "^0.8.6",
"argparse": "~1.0.3",
"base64-js": "^1.5.1",
"dingbat-to-unicode": "^1.0.1",
"jszip": "^3.7.1",
"lop": "^0.4.2",
"underscore": "^1.13.1",
"xmlbuilder": "^10.0.0"
},
"bin": {
"mammoth": "bin/mammoth"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -568,6 +693,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/option": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
"integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==",
"license": "BSD-2-Clause"
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -583,10 +720,28 @@
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/pdfjs-dist": {
"version": "6.3.289",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.3.289.tgz",
"integrity": "sha512-ZHjSVpDa3D6izMq8/04lvkhkATUmL9px6ChPaXc1k6nU2Mrhlg1/7F0bdUqCwUjw3NsPTfPZsMDUU6ZIcRaeQw==",
"license": "Apache-2.0",
"engines": {
"node": ">=22.13.0 || >=24"
},
"optionalDependencies": {
"@napi-rs/canvas": "^1.0.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"forwarded": "0.2.0", "forwarded": "0.2.0",
@@ -594,12 +749,16 @@
}, },
"engines": { "engines": {
"node": ">= 0.10" "node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.15.3", "version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"es-define-property": "^1.0.1", "es-define-property": "^1.0.1",
@@ -636,6 +795,27 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -707,6 +887,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -785,6 +971,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -794,6 +986,21 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -816,6 +1023,21 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/underscore": {
"version": "1.13.8",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
"license": "MIT"
},
"node_modules/undici": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
}
},
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -825,6 +1047,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": { "node_modules/utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -842,6 +1070,15 @@
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
},
"node_modules/xmlbuilder": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
"integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
} }
} }
} }
+533
View File
@@ -0,0 +1,533 @@
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.15](https://github.com/xmldom/xmldom/compare/0.8.14...0.8.15)
### Fixed
- Security: parsing a deeply or repeatedly namespaced document no longer consumes quadratic memory; the in-scope namespace map is inherited through the prototype chain instead of being copied for every prefix-declaring element (O(N) instead of O(N²)), preventing a denial-of-service reachable from `DOMParser.parseFromString` with default options. Serialized output is byte-identical. [`GHSA-965w-775f-mr7g`](https://github.com/xmldom/xmldom/security/advisories/GHSA-965w-775f-mr7g)
- Security: attribute de-duplication during parsing is now O(M) instead of O(M²); the `NamedNodeMap` parse-time dedup path uses a null-prototype membership index, so a well-formed document with a hostile number of duplicate attributes can no longer wedge the parse. Attribute order and duplicate resolution (last value wins, first position kept) are byte-identical, preserving the XML [no-duplicate-attributes well-formedness constraint](https://www.w3.org/TR/xml/#uniqattspec). [`GHSA-8344-3jmq-59r6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6)
- Security: trimming trailing whitespace from an XML end tag ([`ETag`](https://www.w3.org/TR/xml/#NT-ETag)) is now anchored so it runs in linear time instead of backtracking quadratically on a long whitespace run, preventing a ReDoS reachable from `DOMParser.parseFromString`. Trimmed output is byte-identical. [`GHSA-x4fp-j954-r2f4`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x4fp-j954-r2f4)
- Security: malformed-input recovery is now linear instead of quadratic — the malformed tag-name scan terminates at an embedded `<`, and `Node.prototype.normalize()` merges adjacent text nodes in O(K) instead of O(K²) (also reachable programmatically), per [`normalize()`](https://dom.spec.whatwg.org/#dom-node-normalize) in the WHATWG DOM spec. DOM output is unchanged; only the reported error text differs. [`GHSA-93r5-fhx6-vmg9`](https://github.com/xmldom/xmldom/security/advisories/GHSA-93r5-fhx6-vmg9)
- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now rejects a DocType `name` that is not a valid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name), throwing `InvalidStateError` — matching the sibling `publicId`/`systemId`/`internalSubset` checks and preventing XML injection via `DocumentType.name`. [`GHSA-27p8-2357-5qqv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv)
- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now validates a processing-instruction target as an XML [`NCName`](https://www.w3.org/TR/xml-names/#NT-NCName) and rejects a case-insensitive `xml`, throwing `InvalidStateError` — a check `0.8.x` did not previously perform, preventing PI-target injection via `>`, `?`, or whitespace. [`GHSA-c7q8-3ch8-vqpv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-c7q8-3ch8-vqpv)
- Security: `Document.createEntityReference()` now rejects an invalid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name) at creation, and `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` validates an `EntityReference` `nodeName` as an XML `Name`, throwing `InvalidStateError` — preventing XML injection via an entity-reference name. [`GHSA-6gmq-8vp8-gcm6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6gmq-8vp8-gcm6)
- Security: the parser now reports a not-well-formed end tag whose valid name is followed by trailing content as a recoverable `error` instead of accepting it silently, per the XML [`ETag`](https://www.w3.org/TR/xml/#NT-ETag) production; parsing recovers to the byte-identical DOM. Consumers that want strict rejection can escalate the reported `error` to fatal via the parser's `errorHandler`. [`GHSA-6h8r-xr42-gp59`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6h8r-xr42-gp59)
Thank you,
[@ericchiang](https://github.com/ericchiang),
[@bhaswanthc](https://github.com/bhaswanthc),
[@arpitjain099](https://github.com/arpitjain099),
[@Paranoidgrinch](https://github.com/Paranoidgrinch),
for your contributions
## [0.8.14](https://github.com/xmldom/xmldom/compare/0.8.13...0.8.14)
### Fixed
- Security: `XMLSerializer.serializeToString()` now also rejects invalid element and attribute names when `{ requireWellFormed: true }` is passed, throwing `InvalidStateError` for a name that is not a valid XML [`QName`](https://www.w3.org/TR/xml-names/#NT-QName) (this covers the namespace prefix, which surfaces in the element qualified name or in a synthesized `xmlns:` declaration). This prevents XML injection via `createElement()` / `setAttribute()`, extending the existing `requireWellFormed` checks to the serialized name set. [`GHSA-w2rr-34g9-rvrj`](https://github.com/xmldom/xmldom/security/advisories/GHSA-w2rr-34g9-rvrj) [`GHSA-4w3w-2rp5-g8jm`](https://github.com/xmldom/xmldom/security/advisories/GHSA-4w3w-2rp5-g8jm)
Thank you,
[@bhaswanthc](https://github.com/bhaswanthc),
[@jmestwa-coder](https://github.com/jmestwa-coder),
for your contributions
## [0.8.13](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)
### Fixed
- Security: `XMLSerializer.serializeToString()` (and `Node.toString()`, `NodeList.toString()`) now accept a `requireWellFormed` option (fourth argument, after `isHtml` and `nodeFilter`). When `{ requireWellFormed: true }` is passed, the serializer throws `InvalidStateError` for injection-prone node content, preventing XML injection via attacker-controlled node data. [`GHSA-j759-j44w-7fr8`](https://github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8) [`GHSA-x6wf-f3px-wcqx`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x6wf-f3px-wcqx) [`GHSA-f6ww-3ggp-fr8h`](https://github.com/xmldom/xmldom/security/advisories/GHSA-f6ww-3ggp-fr8h)
- Comment: throws when `data` contains `-->`
- ProcessingInstruction: throws when `data` contains `?>`
- DocumentType: throws when `publicId` fails `PubidLiteral`, `systemId` fails `SystemLiteral`, or `internalSubset` contains `]>`
- Security: DOM traversal operations (`XMLSerializer.serializeToString()`, `Node.prototype.normalize()`, `Node.prototype.cloneNode(true)`, `Document.prototype.importNode(node, true)`, `node.textContent` getter, `getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()`) are now iterative. Previously, deeply nested DOM trees would exhaust the JavaScript call stack and throw an unrecoverable `RangeError`. [`GHSA-2v35-w6hq-6mfw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-2v35-w6hq-6mfw)
Thank you,
[@Jvr2022](https://github.com/Jvr2022),
[@praveen-kv](https://github.com/praveen-kv),
[@TharVid](https://github.com/TharVid),
[@decsecre583](https://github.com/decsecre583),
[@tlsbollei](https://github.com/tlsbollei),
[@KarimTantawey](https://github.com/KarimTantawey),
for your contributions
## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)
### Fixed
- preserve trailing whitespace in ProcessingInstruction data [`#962`](https://github.com/xmldom/xmldom/pull/962) / [`#42`](https://github.com/xmldom/xmldom/issues/42)
- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp)
Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data.
Thank you,
[@thesmartshadow](https://github.com/thesmartshadow),
[@stevenobiajulu](https://github.com/stevenobiajulu),
for your contributions
## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11)
### Fixed
- update `ownerDocument` when moving nodes between documents [`#933`](https://github.com/xmldom/xmldom/pull/933) / [`#932`](https://github.com/xmldom/xmldom/issues/932)
Thank you, [@shunkica](https://github.com/shunkica), for your contributions
## [0.8.10](https://github.com/xmldom/xmldom/compare/0.8.9...0.8.10)
### Fixed
- dom: prevent iteration over deleted items [`#514`](https://github.com/xmldom/xmldom/pull/514)/ [`#499`](https://github.com/xmldom/xmldom/issues/499)
Thank you, [@qtow](https://github.com/qtow), for your contributions
## [0.8.9](https://github.com/xmldom/xmldom/compare/0.8.8...0.8.9)
### Fixed
- Set nodeName property in ProcessingInstruction [`#509`](https://github.com/xmldom/xmldom/pull/509) / [`#505`](https://github.com/xmldom/xmldom/issues/505)
Thank you, [@cjbarth](https://github.com/cjbarth), for your contributions
## [0.8.8](https://github.com/xmldom/xmldom/compare/0.8.7...0.8.8)
### Fixed
- extend list of HTML entities [`#489`](https://github.com/xmldom/xmldom/pull/489)
Thank you, [@zorkow](https://github.com/zorkow), for your contributions
## [0.8.7](https://github.com/xmldom/xmldom/compare/0.8.6...0.8.7)
### Fixed
- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486)
Thank you, [@bulandent](https://github.com/bulandent), for your contributions
## [0.7.10](https://github.com/xmldom/xmldom/compare/0.7.9...0.7.10)
### Fixed
- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486)
Thank you, [@bulandent](https://github.com/bulandent), for your contributions
## [0.8.6](https://github.com/xmldom/xmldom/compare/0.8.5...0.8.6)
### Fixed
- Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456)
Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions
## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5)
### Fixed
- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453)
Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions
## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4)
### Fixed
- Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883)
In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly.
In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead.
This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior.
Related Spec: <https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity>
Thank you, [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions
## [0.8.3](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.2)
### Fixed
- Avoid iterating over prototype properties [`#437`](https://github.com/xmldom/xmldom/pull/437) / [`#436`](https://github.com/xmldom/xmldom/issues/436)
Thank you, [@Supraja9726](https://github.com/Supraja9726) for your contributions
## [0.8.2](https://github.com/xmldom/xmldom/compare/0.8.1...0.8.2)
### Fixed
- fix(dom): Serialize `&gt;` as specified (#395) [`#58`](https://github.com/xmldom/xmldom/issues/58)
### Other
- docs: Add `nodeType` values to public interface description [`#396`](https://github.com/xmldom/xmldom/pull/396)
- test: Add executable examples for node and typescript [`#317`](https://github.com/xmldom/xmldom/pull/317)
- fix(dom): Serialize `&gt;` as specified [`#395`](https://github.com/xmldom/xmldom/pull/395)
- chore: Add minimal `Object.assign` ponyfill [`#379`](https://github.com/xmldom/xmldom/pull/379)
- docs: Refine release documentation [`#378`](https://github.com/xmldom/xmldom/pull/378)
- chore: update various dev dependencies
Thank you [@niklasl](https://github.com/niklasl), [@cburatto](https://github.com/cburatto), [@SheetJSDev](https://github.com/SheetJSDev), [@pyrsmk](https://github.com/pyrsmk) for your contributions
## [0.8.1](https://github.com/xmldom/xmldom/compare/0.8.0...0.8.1)
### Fixes
- Only use own properties in entityMap [`#374`](https://github.com/xmldom/xmldom/pull/374)
### Docs
- Add security policy [`#365`](https://github.com/xmldom/xmldom/pull/365)
- changelog: Correct contributor name and link [`#366`](https://github.com/xmldom/xmldom/pull/366)
- Describe release/publish steps [`#358`](https://github.com/xmldom/xmldom/pull/358), [`#376`](https://github.com/xmldom/xmldom/pull/376)
- Add snyk package health badge [`#360`](https://github.com/xmldom/xmldom/pull/360)
## [0.8.0](https://github.com/xmldom/xmldom/compare/0.7.5...0.8.0)
### Fixed
- Normalize all line endings according to XML specs [1.0](https://w3.org/TR/xml/#sec-line-ends) and [1.1](https://www.w3.org/TR/xml11/#sec-line-ends) \
BREAKING CHANGE: Certain combination of line break characters are normalized to a single `\n` before parsing takes place and will no longer be preserved.
- [`#303`](https://github.com/xmldom/xmldom/issues/303) / [`#307`](https://github.com/xmldom/xmldom/pull/307)
- [`#49`](https://github.com/xmldom/xmldom/issues/49), [`#97`](https://github.com/xmldom/xmldom/issues/97), [`#324`](https://github.com/xmldom/xmldom/issues/324) / [`#314`](https://github.com/xmldom/xmldom/pull/314)
- XMLSerializer: Preserve whitespace character references [`#284`](https://github.com/xmldom/xmldom/issues/284) / [`#310`](https://github.com/xmldom/xmldom/pull/310) \
BREAKING CHANGE: If you relied on the not spec compliant preservation of literal `\t`, `\n` or `\r` in **attribute values**.
To preserve those you will have to create XML that instead contains the correct numerical (or hexadecimal) equivalent (e.g. `&#x9;`, `&#xA;`, `&#xD;`).
- Drop deprecated exports `DOMImplementation` and `XMLSerializer` from `lib/dom-parser.js` [#53](https://github.com/xmldom/xmldom/issues/53) / [`#309`](https://github.com/xmldom/xmldom/pull/309)
BREAKING CHANGE: Use the one provided by the main package export.
- dom: Remove all links as part of `removeChild` [`#343`](https://github.com/xmldom/xmldom/issues/343) / [`#355`](https://github.com/xmldom/xmldom/pull/355)
### Chore
- ci: Restore latest tested node version to 16.x [`#325`](https://github.com/xmldom/xmldom/pull/325)
- ci: Split test and lint steps into jobs [`#111`](https://github.com/xmldom/xmldom/issues/111) / [`#304`](https://github.com/xmldom/xmldom/pull/304)
- Pinned and updated devDependencies
Thank you [@marrus-sh](https://github.com/marrus-sh), [@victorandree](https://github.com/victorandree), [@mdierolf](https://github.com/mdierolf), [@tsabbay](https://github.com/tsabbay), [@fatihpense](https://github.com/fatihpense) for your contributions
## 0.7.5
[Commits](https://github.com/xmldom/xmldom/compare/0.7.4...0.7.5)
### Fixes:
- Preserve default namespace when serializing [`#319`](https://github.com/xmldom/xmldom/issues/319) / [`#321`](https://github.com/xmldom/xmldom/pull/321)
Thank you, [@lupestro](https://github.com/lupestro)
## 0.7.4
[Commits](https://github.com/xmldom/xmldom/compare/0.7.3...0.7.4)
### Fixes:
- Restore ability to parse `__prototype__` attributes [`#315`](https://github.com/xmldom/xmldom/pull/315)
Thank you, [@dsimpsonOMF](https://github.com/dsimpsonOMF)
## 0.7.3
[Commits](https://github.com/xmldom/xmldom/compare/0.7.2...0.7.3)
### Fixes:
- Add doctype when parsing from string [`#277`](https://github.com/xmldom/xmldom/issues/277) / [`#301`](https://github.com/xmldom/xmldom/pull/301)
- Correct typo in error message [`#294`](https://github.com/xmldom/xmldom/pull/294)
Thank you, [@rrthomas](https://github.com/rrthomas)
### Refactor:
- Improve exports & require statements, new main package entry [`#233`](https://github.com/xmldom/xmldom/pull/233)
### Docs:
- Fix Stryker badge [`#298`](https://github.com/xmldom/xmldom/pull/298)
- Fix link to help-wanted issues [`#299`](https://github.com/xmldom/xmldom/pull/299)
### Chore:
- Execute stryker:dry-run on branches [`#302`](https://github.com/xmldom/xmldom/pull/302)
- Fix stryker config [`#300`](https://github.com/xmldom/xmldom/pull/300)
- Split test and lint scripts [`#297`](https://github.com/xmldom/xmldom/pull/297)
- Switch to stryker dashboard owned by org [`#292`](https://github.com/xmldom/xmldom/pull/292)
## 0.7.2
[Commits](https://github.com/xmldom/xmldom/compare/0.7.1...0.7.2)
### Fixes:
- Types: Add index.d.ts to packaged files [`#288`](https://github.com/xmldom/xmldom/pull/288)
Thank you, [@forty](https://github.com/forty)
## 0.7.1
[Commits](https://github.com/xmldom/xmldom/compare/0.7.0...0.7.1)
### Fixes:
- Types: Copy types from DefinitelyTyped [`#283`](https://github.com/xmldom/xmldom/pull/283)
Thank you, [@kachkaev](https://github.com/kachkaev)
### Chore:
- package.json: remove author, maintainers, etc. [`#279`](https://github.com/xmldom/xmldom/pull/279)
## 0.7.0
[Commits](https://github.com/xmldom/xmldom/compare/0.6.0...0.7.0)
Due to [`#271`](https://github.com/xmldom/xmldom/issue/271) this version was published as
- unscoped `xmldom` package to github (git tags [`0.7.0`](https://github.com/xmldom/xmldom/tree/0.7.0) and [`0.7.0+unscoped`](https://github.com/xmldom/xmldom/tree/0.7.0%2Bunscoped))
- scoped `@xmldom/xmldom` package to npm (git tag `0.7.0+scoped`)
For more details look at [`#278`](https://github.com/xmldom/xmldom/pull/278#issuecomment-902172483)
### Fixes:
- Security: Misinterpretation of malicious XML input [`CVE-2021-32796`](https://github.com/xmldom/xmldom/security/advisories/GHSA-5fg8-2547-mr8q)
- Implement `Document.getElementsByClassName` as specified [`#213`](https://github.com/xmldom/xmldom/pull/213), thank you, [@ChALkeR](https://github.com/ChALkeR)
- Inherit namespace prefix from parent when required [`#268`](https://github.com/xmldom/xmldom/pull/268)
- Handle whitespace in closing tags [`#267`](https://github.com/xmldom/xmldom/pull/267)
- Update `DOMImplementation` according to recent specs [`#210`](https://github.com/xmldom/xmldom/pull/210)
BREAKING CHANGE: Only if you "passed features to be marked as available as a constructor arguments" and expected it to "magically work".
- No longer serializes any namespaces with an empty URI [`#244`](https://github.com/xmldom/xmldom/pull/244)
(related to [`#168`](https://github.com/xmldom/xmldom/pull/168) released in 0.6.0)
BREAKING CHANGE: Only if you rely on ["unsetting" a namespace prefix](https://github.com/xmldom/xmldom/pull/168#issuecomment-886984994) by setting it to an empty string
- Set `localName` as part of `Document.createElement` [`#229`](https://github.com/xmldom/xmldom/pull/229), thank you, [@rrthomas](https://github.com/rrthomas)
### CI
- We are now additionally running tests against node v16
- Stryker tests on the master branch now run against node v14
### Docs
- Describe relations with and between specs: [`#211`](https://github.com/xmldom/xmldom/pull/211), [`#247`](https://github.com/xmldom/xmldom/pull/247)
## 0.6.0
[Commits](https://github.com/xmldom/xmldom/compare/0.5.0...0.6.0)
### Fixes
- Stop serializing empty namespace values like `xmlns:ds=""` [`#168`](https://github.com/xmldom/xmldom/pull/168)
BREAKING CHANGE: If your code expected empty namespaces attributes to be serialized.
Thank you, [@pdecat](https://github.com/pdecat) and [@FranckDepoortere](https://github.com/FranckDepoortere)
- Escape `<` to `&lt;` when serializing attribute values [`#198`](https://github.com/xmldom/xmldom/issues/198) / [`#199`](https://github.com/xmldom/xmldom/pull/199)
## 0.5.0
[Commits](https://github.com/xmldom/xmldom/compare/0.4.0...0.5.0)
### Fixes
- Avoid misinterpretation of malicious XML input - [`GHSA-h6q6-9hqw-rwfv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-h6q6-9hqw-rwfv) (CVE-2021-21366)
- Improve error reporting; throw on duplicate attribute\
BREAKING CHANGE: It is currently not clear how to consistently deal with duplicate attributes, so it's also safer for our users to fail when detecting them.
It's possible to configure the `DOMParser.errorHandler` before parsing, to handle those errors differently.
To accomplish this and also be able to verify it in tests I needed to
- create a new `Error` type `ParseError` and export it
- Throw `ParseError` from `errorHandler.fatalError` and prevent those from being caught in `XMLReader`.
- export `DOMHandler` constructor as `__DOMHandler`
- Preserve quotes in DOCTYPE declaration
Since the only purpose of parsing the DOCTYPE is to be able to restore it when serializing, we decided that it would be best to leave the parsed `publicId` and `systemId` as is, including any quotes.
BREAKING CHANGE: If somebody relies on the actual unquoted values of those ids, they will need to take care of either single or double quotes and the right escaping.
(Without this change this would not have been possible because the SAX parser already dropped the information about the quotes that have been used in the source.)
https://www.w3.org/TR/2006/REC-xml11-20060816/#dtd
https://www.w3.org/TR/2006/REC-xml11-20060816/#IDAX1KS (External Entity Declaration)
- Fix breaking preprocessors' directives when parsing attributes [`#171`](https://github.com/xmldom/xmldom/pull/171)
- fix(dom): Escape `]]&gt;` when serializing CharData [`#181`](https://github.com/xmldom/xmldom/pull/181)
- Switch to (only) MIT license (drop problematic LGPL license option) [`#178`](https://github.com/xmldom/xmldom/pull/178)
- Export DOMException; remove custom assertions; etc. [`#174`](https://github.com/xmldom/xmldom/pull/174)
### Docs
- Update MDN links in `readme.md` [`#188`](https://github.com/xmldom/xmldom/pull/188)
## 0.4.0
[Commits](https://github.com/xmldom/xmldom/compare/0.3.0...0.4.0)
### Fixes
- **BREAKING** Restore `&nbsp;` behavior from v0.1.27 [`#67`](https://github.com/xmldom/xmldom/pull/67)
- **BREAKING** Typecheck source param before parsing [`#113`](https://github.com/xmldom/xmldom/pull/113)
- Include documents in package files list [`#156`](https://github.com/xmldom/xmldom/pull/156)
- Preserve doctype with sysid [`#144`](https://github.com/xmldom/xmldom/pull/144)
- Remove ES6 syntax from getElementsByClassName [`#91`](https://github.com/xmldom/xmldom/pull/91)
- Revert "Add lowercase of åäö in entityMap" due to duplicate entries [`#84`](https://github.com/xmldom/xmldom/pull/84)
- fix: Convert all line separators to LF [`#66`](https://github.com/xmldom/xmldom/pull/66)
### Docs
- Update CHANGELOG.md through version 0.3.0 [`#63`](https://github.com/xmldom/xmldom/pull/63)
- Update badges [`#78`](https://github.com/xmldom/xmldom/pull/78)
- Add .editorconfig file [`#104`](https://github.com/xmldom/xmldom/pull/104)
- Add note about import [`#79`](https://github.com/xmldom/xmldom/pull/79)
- Modernize & improve the example in readme.md [`#81`](https://github.com/xmldom/xmldom/pull/81)
### CI
- Add Stryker Mutator [`#70`](https://github.com/xmldom/xmldom/pull/70)
- Add Stryker action to update dashboard [`#77`](https://github.com/xmldom/xmldom/pull/77)
- Add Node GitHub action workflow [`#64`](https://github.com/xmldom/xmldom/pull/64)
- add & enable eslint [`#106`](https://github.com/xmldom/xmldom/pull/106)
- Use eslint-plugin-es5 to enforce ES5 syntax [`#107`](https://github.com/xmldom/xmldom/pull/107)
- Recover `vows` tests, drop `proof` tests [`#59`](https://github.com/xmldom/xmldom/pull/59)
- Add jest tessuite and first tests [`#114`](https://github.com/xmldom/xmldom/pull/114)
- Add jest testsuite with `xmltest` cases [`#112`](https://github.com/xmldom/xmldom/pull/112)
- Configure Renovate [`#108`](https://github.com/xmldom/xmldom/pull/108)
- Test European HTML entities [`#86`](https://github.com/xmldom/xmldom/pull/86)
- Updated devDependencies
### Other
- Remove files that are not of any use [`#131`](https://github.com/xmldom/xmldom/pull/131), [`#65`](https://github.com/xmldom/xmldom/pull/65), [`#33`](https://github.com/xmldom/xmldom/pull/33)
## 0.3.0
[Commits](https://github.com/xmldom/xmldom/compare/0.2.1...0.3.0)
- **BREAKING** Node >=10.x is now required.
- **BREAKING** Remove `component.json` (deprecated package manager https://github.com/componentjs/guide)
- **BREAKING** Move existing sources into `lib` subdirectory.
- **POSSIBLY BREAKING** Introduce `files` entry in `package.json` and remove use of `.npmignore`.
- [Add `Document.getElementsByClassName`](https://github.com/xmldom/xmldom/issues/24).
- [Add `Node` to the list of exports](https://github.com/xmldom/xmldom/pull/27)
- [Add lowercase of åäö in `entityMap`](https://github.com/xmldom/xmldom/pull/23).
- Move CHANGELOG to markdown file.
- Move LICENSE to markdown file.
## 0.2.1
[Commits](https://github.com/xmldom/xmldom/compare/0.2.0...0.2.1)
- Correct `homepage`, `repository` and `bugs` URLs in `package.json`.
## 0.2.0
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...0.2.0)
- Includes all **BREAKING** changes introduced in [`xmldom-alpha@v0.1.28`](#0128) by the original authors.
- **POSSIBLY BREAKING** [remove the `Object.create` check from the `_extends` method of `dom.js` that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/0be2ae910a8a22c9ec2cac042e04de4c04317d2a#diff-7d1c5d97786fdf9af5446a241d0b6d56L19-L22) ().
- **POSSIBLY BREAKING** [remove code that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/366159a76a181ce9a0d83f5dc48205686cfaf9cc)
- formatting/corrections in `package.json`
## 0.1.31
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...v0.1.31)
The patch versions (`v0.1.29` - `v0.1.31`) that have been released on the [v0.1.x branch](https://github.com/xmldom/xmldom/tree/0.1.x), to reflect the changed maintainers, **are branched off from [`v0.1.27`](#0127) so they don't include the breaking changes introduced in [`xmldom-alpha@v0.1.28`](#0128)**:
## Maintainer changes
After the last commit to the original repository <https://github.com/jindw/xmldom> on the 9th of May 2017, the first commit to <https://github.com/xmldom/xmldom> is from the 19th of December 2019. [The fork has been announced in the original repository on the 2nd of March 2020.](https://github.com/jindw/xmldom/issues/259)
The versions listed below have been published to one or both of the following packages:
- <https://www.npmjs.com/package/xmldom-alpha>
- <https://www.npmjs.com/package/xmldom>
It is currently not planned to continue publishing the `xmldom-alpha` package.
The new maintainers did not invest time to understand changes that led to the last `xmldom` version [`0.1.27`](#0127) published by the original maintainer, but consider it the basis for their work.
A timeline of all the changes that happened from that version until `0.3.0` is available in <https://github.com/xmldom/xmldom/issues/62>. Any related questions should be asked there.
## 0.1.28
[Commits](https://github.com/xmldom/xmldom/compare/v0.1.27...xmldom-alpha@v0.1.28)
Published by @jindw on the 9th of May 2017 as
- `xmldom-alpha@0.1.28`
- **BREAKING** includes [regression regarding `&nbsp;` (issue #57)](https://github.com/xmldom/xmldom/issues/57)
- [Fix `license` field in `package.json`](https://github.com/jindw/xmldom/pull/178)
- [Conditional converting of HTML entities](https://github.com/jindw/xmldom/pull/80)
- Fix `dom.js` serialization issue for missing document element ([example that failed on `toString()` before this change](https://github.com/xmldom/xmldom/blob/a58dcf7a265522e80ce520fe3be0cddb1b976f6f/test/parse/unclosedcomment.js#L10-L11))
- Add new module `entities.js`
## 0.1.27
Published by @jindw on the 28th of Nov 2016 as
- `xmldom@0.1.27`
- `xmldom-alpha@0.1.27`
- Various bug fixes.
## 0.1.26
Published on the 18th of Nov 2016
as `xmldom@0.1.26`
- Details unknown
## 0.1.25
Published on the 18th of Nov 2016 as
- `xmldom@0.1.25`
- Details unknown
## 0.1.24
Published on the 27th of November 2016 as
- `xmldom@0.1.24`
- `xmldom-alpha@0.1.24`
- Added node filter.
## 0.1.23
Published on the 5th of May 2016 as
- `xmldom-alpha@0.1.23`
- Add namespace support for nest node serialize.
- Various other bug fixes.
## 0.1.22
- Merge XMLNS serialization.
- Remove \r from source string.
- Print namespaces for child elements.
- Switch references to nodeType to use named constants.
- Add nodelist toString support.
## 0.1.21
- Fix serialize bug.
## 0.1.20
- Optimize invalid XML support.
- Add toString sorter for attributes output.
- Add html self closed node button.
- Add `*` NS support for getElementsByTagNameNS.
- Convert attribute's value to string in setAttributeNS.
- Add support for HTML entities for HTML docs only.
- Fix TypeError when Document is created with DocumentType.
## 0.1.19
- Fix [infinite loop on unclosed comment (jindw/xmldom#68)](https://github.com/jindw/xmldom/issues/68)
- Add error report for unclosed tag.
- Various other fixes.
## 0.1.18
- Add default `ns` support.
- parseFromString now renders entirely plain text documents as textNode.
- Enable option to ignore white space on parsing.
## 0.1.17
**Details missing for this and potential earlier version**
## 0.1.16
- Correctly handle multibyte Unicode greater than two byts. #57. #56.
- Initial unit testing and test coverage. #53. #46. #19.
- Create Bower `component.json` #52.
## 0.1.8
- Add: some test case from node-o3-xml(excludes xpath support)
- Fix: remove existed attribute before setting (bug introduced in v0.1.5)
- Fix: index direct access for childNodes and any NodeList collection(not w3c standard)
- Fix: remove last child bug
+8
View File
@@ -0,0 +1,8 @@
Copyright 2019 - present Christopher J. Brody and other contributors, as listed in: https://github.com/xmldom/xmldom/graphs/contributors
Copyright 2012 - 2017 @jindw <jindw@xidea.org> and other contributors, as listed in: https://github.com/jindw/xmldom/graphs/contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+50
View File
@@ -0,0 +1,50 @@
# Security Policy
The most up-to-date version of this document can be found at <https://github.com/xmldom/xmldom/security/policy>.
## Supported Versions
This repository contains the code for the libraries `xmldom` and `@xmldom/xmldom` on npm.
As long as we didn't publish v1, we aim to maintain the last two minor versions with security fixes. If it is possible we provide security fixes as path versions.
If you think there is a good reason to also patch an earlier version let us know in a github issue or the release discussion once the fix has been provided.
The maintainers will consider it and if we agree and have/find the required resources, a patch for that version will be provided.
Please notice that [we are no longer able to publish the (unscoped) `xmldom` package](https://github.com/xmldom/xmldom/issues/271),
and that all existing versions of `xmldom` are affected by at least one security vulnerability and should be considered deprecated.
You can still report issues regarding `xmldom` as described below.
If you need help with migrating from `xmldom` to `@xmldom/xmldom`, file a github issue or PR in the affected repository and mention @karfau.
## Reporting vulnerabilities
Please email reports about any security related issues you find to `security@xmldom.org`, which will forward it to the list of maintainers.
The maintainers will try to respond within 7 calendar days. (If nobody peplies after 7 days, please us send a reminder!)
As part of you communication please make sure to always hit "Reply all", so all maintainers are kept in the loop.
In addition, please include the following information along with your report:
- Your name and affiliation (if any).
- A description of the technical details of the vulnerabilities. It is very important to let us know how we can reproduce your findings.
- An explanation who can exploit this vulnerability, and what they gain when doing so -- write an attack scenario. This will help us evaluate your report quickly, especially if the issue is complex.
- Whether this vulnerability public or known to third parties. If it is, please provide details.
If you believe that an existing (public) issue is security-related, please send an email to `security@xmldom.org`.
The email should include the issue URL and a short description of why it should be handled according to this security policy.
Once an issue is reported, the maintainers use the following disclosure process:
- When a report is received, we confirm the issue, determine its severity and the affected versions.
- If we know of specific third-party services or software based on xmldom that require mitigation before publication, those projects will be notified.
- A [github security advisory](https://docs.github.com/en/code-security/security-advisories/about-github-security-advisories) is [created](https://docs.github.com/en/code-security/security-advisories/creating-a-security-advisory) (but not published) which details the problem and steps for mitigation.
- If the reporter provides a github account and agrees to it, we (add that github account as a collaborator on the advisuory)[https://docs.github.com/en/code-security/security-advisories/adding-a-collaborator-to-a-security-advisory].
- The vulnerability is fixed in a [private fork](https://docs.github.com/en/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability) and potential workarounds are identified.
- The maintainers audit the existing code to find any potential similar problems.
- The release for the current minor version and the [security advisory are published](https://docs.github.com/en/code-security/security-advisories/publishing-a-security-advisory).
- The release(s) for previous minor version(s) are published.
We credit reporters for identifying security issues, if they confirm that they want to.
## Known vulnerabilities
See https://github.com/xmldom/xmldom/security/advisories?state=published
+96
View File
@@ -0,0 +1,96 @@
/// <reference lib="dom" />
declare module "@xmldom/xmldom" {
var DOMParser: DOMParserStatic;
var XMLSerializer: XMLSerializerStatic;
var DOMImplementation: DOMImplementationStatic;
interface DOMImplementationStatic {
new(): DOMImplementation;
}
interface DOMParserStatic {
new (): DOMParser;
new (options: Options): DOMParser;
}
interface XMLSerializerStatic {
new (): XMLSerializer;
}
interface DOMParser {
parseFromString(xmlsource: string, mimeType?: string): Document;
}
/** Options accepted by `XMLSerializer.prototype.serializeToString`. */
interface XMLSerializerOptions {
/**
* When `true`, the serializer throws a DOMException with code `INVALID_STATE_ERR` if:
* - A CDATASection node's data contains `"]]>"`.
* - A Comment node's data contains `"-->"` (the injection sequence that terminates a
* comment). Comments whose data contains `"--"` but not `"-->"` are accepted on this
* branch — the 0.8.x parser does not validate bare `"--"` in comment content.
* - A ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon)
* or is an ASCII case-insensitive match for `"xml"`, or its data contains `"?>"`
* (W3C DOM Parsing §3.2.1.7).
* - An EntityReference's `nodeName` is not a valid XML `Name`.
*
* @default false
*/
requireWellFormed?: boolean;
}
interface XMLSerializer {
/**
* Returns the result of serializing `node` to XML.
*
* When `options.requireWellFormed` is `true`, the serializer throws for content that would
* produce ill-formed XML.
*
* __This implementation differs from the specification:__
* - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
* at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
* default behaviour) unless `requireWellFormed` is `true`.
* - when `requireWellFormed` is `true`, `DOMException` with code `INVALID_STATE_ERR`
* is only thrown to prevent injection vectors, not for all the spec mandated checks.
*
* @throws {DOMException}
* With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and:
* - an Element's qualified name (including any namespace prefix) is not a valid XML QName,
* - an attribute's qualified name (including a synthesized `xmlns:` namespace declaration)
* is not a valid XML QName,
* - a CDATASection node's data contains `"]]>"`,
* - a Comment node's data contains `"-->"` (bare `"--"` does not throw on this branch),
* - a ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon) or
* is an ASCII case-insensitive match for `"xml"`, or its data contains `"?>"`,
* - a DocumentType's `name` is not a valid XML `Name` (XML 1.0 production [5]),
* - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral`
* production,
* - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral`
* production,
* - a DocumentType's `internalSubset` contains `"]>"`, or
* - an EntityReference's `nodeName` is not a valid XML `Name` (XML 1.0 production [5]).
* Note: xmldom does not enforce `readonly` on DocumentType fields — direct property
* writes succeed and are covered by the serializer-level checks above.
* @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
* @see https://w3c.github.io/DOM-Parsing/#xml-serialization
* @see https://github.com/w3c/DOM-Parsing/issues/84
*/
serializeToString(node: Node, isHtml?: boolean, nodeFilter?: (node: Node) => Node | null | undefined, options?: XMLSerializerOptions): string;
}
interface Options {
locator?: any;
errorHandler?: ErrorHandlerFunction | ErrorHandlerObject | undefined;
}
interface ErrorHandlerFunction {
(level: string, msg: any): any;
}
interface ErrorHandlerObject {
warning?: ((msg: any) => any) | undefined;
error?: ((msg: any) => any) | undefined;
fatalError?: ((msg: any) => any) | undefined;
}
}
+2
View File
@@ -0,0 +1,2 @@
extends:
- 'plugin:es5/no-es2015'
+214
View File
@@ -0,0 +1,214 @@
'use strict'
/**
* Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
*
* Works with anything that has a `length` property and index access properties, including NodeList.
*
* @template {unknown} T
* @param {Array<T> | ({length:number, [number]: T})} list
* @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
* @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
* allows injecting a custom implementation in tests
* @returns {T | undefined}
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
* @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
*/
function find(list, predicate, ac) {
if (ac === undefined) {
ac = Array.prototype;
}
if (list && typeof ac.find === 'function') {
return ac.find.call(list, predicate);
}
for (var i = 0; i < list.length; i++) {
if (Object.prototype.hasOwnProperty.call(list, i)) {
var item = list[i];
if (predicate.call(undefined, item, i, list)) {
return item;
}
}
}
}
/**
* "Shallow freezes" an object to render it immutable.
* Uses `Object.freeze` if available,
* otherwise the immutability is only in the type.
*
* Is used to create "enum like" objects.
*
* @template T
* @param {T} object the object to freeze
* @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
* allows to inject custom object constructor for tests
* @returns {Readonly<T>}
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
*/
function freeze(object, oc) {
if (oc === undefined) {
oc = Object
}
return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object
}
/**
* Since we can not rely on `Object.assign` we provide a simplified version
* that is sufficient for our needs.
*
* @param {Object} target
* @param {Object | null | undefined} source
*
* @returns {Object} target
* @throws TypeError if target is not an object
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
* @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
*/
function assign(target, source) {
if (target === null || typeof target !== 'object') {
throw new TypeError('target is not an object')
}
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
return target
}
/**
* All mime types that are allowed as input to `DOMParser.parseFromString`
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
* @see DOMParser.prototype.parseFromString
*/
var MIME_TYPE = freeze({
/**
* `text/html`, the only mime type that triggers treating an XML document as HTML.
*
* @see DOMParser.SupportedType.isHTML
* @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
* @see https://en.wikipedia.org/wiki/HTML Wikipedia
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
*/
HTML: 'text/html',
/**
* Helper method to check a mime type if it indicates an HTML document
*
* @param {string} [value]
* @returns {boolean}
*
* @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
* @see https://en.wikipedia.org/wiki/HTML Wikipedia
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */
isHTML: function (value) {
return value === MIME_TYPE.HTML
},
/**
* `application/xml`, the standard mime type for XML documents.
*
* @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
* @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
* @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
*/
XML_APPLICATION: 'application/xml',
/**
* `text/html`, an alias for `application/xml`.
*
* @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
* @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
* @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
*/
XML_TEXT: 'text/xml',
/**
* `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
* but is parsed as an XML document.
*
* @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
* @see https://en.wikipedia.org/wiki/XHTML Wikipedia
*/
XML_XHTML_APPLICATION: 'application/xhtml+xml',
/**
* `image/svg+xml`,
*
* @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
* @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
* @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
*/
XML_SVG_IMAGE: 'image/svg+xml',
})
/**
* Namespaces that are used in this code base.
*
* @see http://www.w3.org/TR/REC-xml-names
*/
var NAMESPACE = freeze({
/**
* The XHTML namespace.
*
* @see http://www.w3.org/1999/xhtml
*/
HTML: 'http://www.w3.org/1999/xhtml',
/**
* Checks if `uri` equals `NAMESPACE.HTML`.
*
* @param {string} [uri]
*
* @see NAMESPACE.HTML
*/
isHTML: function (uri) {
return uri === NAMESPACE.HTML
},
/**
* The SVG namespace.
*
* @see http://www.w3.org/2000/svg
*/
SVG: 'http://www.w3.org/2000/svg',
/**
* The `xml:` namespace.
*
* @see http://www.w3.org/XML/1998/namespace
*/
XML: 'http://www.w3.org/XML/1998/namespace',
/**
* The `xmlns:` namespace
*
* @see https://www.w3.org/2000/xmlns/
*/
XMLNS: 'http://www.w3.org/2000/xmlns/',
})
//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
//[5] Name ::= NameStartChar (NameChar)*
var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF
var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$');
//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
exports.assign = assign;
exports.find = find;
exports.freeze = freeze;
exports.MIME_TYPE = MIME_TYPE;
exports.NAMESPACE = NAMESPACE;
exports.nameStartChar = nameStartChar;
exports.nameChar = nameChar;
exports.tagNamePattern = tagNamePattern;
+322
View File
@@ -0,0 +1,322 @@
var conventions = require("./conventions");
var dom = require('./dom')
var entities = require('./entities');
var sax = require('./sax');
var DOMImplementation = dom.DOMImplementation;
var NAMESPACE = conventions.NAMESPACE;
var ParseError = sax.ParseError;
var XMLReader = sax.XMLReader;
/**
* Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
*
* > XML parsed entities are often stored in computer files which,
* > for editing convenience, are organized into lines.
* > These lines are typically separated by some combination
* > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
* >
* > To simplify the tasks of applications, the XML processor must behave
* > as if it normalized all line breaks in external parsed entities (including the document entity)
* > on input, before parsing, by translating all of the following to a single #xA character:
* >
* > 1. the two-character sequence #xD #xA
* > 2. the two-character sequence #xD #x85
* > 3. the single character #x85
* > 4. the single character #x2028
* > 5. any #xD character that is not immediately followed by #xA or #x85.
*
* @param {string} input
* @returns {string}
*/
function normalizeLineEndings(input) {
return input
.replace(/\r[\n\u0085]/g, '\n')
.replace(/[\r\u0085\u2028]/g, '\n')
}
/**
* @typedef Locator
* @property {number} [columnNumber]
* @property {number} [lineNumber]
*/
/**
* @typedef DOMParserOptions
* @property {DOMHandler} [domBuilder]
* @property {Function} [errorHandler]
* @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
* defaults to `normalizeLineEndings`
* @property {Locator} [locator]
* @property {Record<string, string>} [xmlns]
*
* @see normalizeLineEndings
*/
/**
* The DOMParser interface provides the ability to parse XML or HTML source code
* from a string into a DOM `Document`.
*
* _xmldom is different from the spec in that it allows an `options` parameter,
* to override the default behavior._
*
* @param {DOMParserOptions} [options]
* @constructor
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
*/
function DOMParser(options){
this.options = options ||{locator:{}};
}
DOMParser.prototype.parseFromString = function(source,mimeType){
var options = this.options;
var sax = new XMLReader();
var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler
var errorHandler = options.errorHandler;
var locator = options.locator;
var defaultNSMap = options.xmlns||{};
var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1;
var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
if(locator){
domBuilder.setDocumentLocator(locator)
}
sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator);
sax.domBuilder = options.domBuilder || domBuilder;
if(isHTML){
defaultNSMap[''] = NAMESPACE.HTML;
}
defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
var normalize = options.normalizeLineEndings || normalizeLineEndings;
if (source && typeof source === 'string') {
sax.parse(
normalize(source),
defaultNSMap,
entityMap
)
} else {
sax.errorHandler.error('invalid doc source')
}
return domBuilder.doc;
}
function buildErrorHandler(errorImpl,domBuilder,locator){
if(!errorImpl){
if(domBuilder instanceof DOMHandler){
return domBuilder;
}
errorImpl = domBuilder ;
}
var errorHandler = {}
var isCallback = errorImpl instanceof Function;
locator = locator||{}
function build(key){
var fn = errorImpl[key];
if(!fn && isCallback){
fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl;
}
errorHandler[key] = fn && function(msg){
fn('[xmldom '+key+']\t'+msg+_locator(locator));
}||function(){};
}
build('warning');
build('error');
build('fatalError');
return errorHandler;
}
//console.log('#\n\n\n\n\n\n\n####')
/**
* +ContentHandler+ErrorHandler
* +LexicalHandler+EntityResolver2
* -DeclHandler-DTDHandler
*
* DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
* DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
* @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
*/
function DOMHandler() {
this.cdata = false;
}
function position(locator,node){
node.lineNumber = locator.lineNumber;
node.columnNumber = locator.columnNumber;
}
/**
* @see org.xml.sax.ContentHandler#startDocument
* @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
*/
DOMHandler.prototype = {
startDocument : function() {
this.doc = new DOMImplementation().createDocument(null, null, null);
if (this.locator) {
this.doc.documentURI = this.locator.systemId;
}
},
startElement:function(namespaceURI, localName, qName, attrs) {
var doc = this.doc;
var el = doc.createElementNS(namespaceURI, qName||localName);
var len = attrs.length;
appendElement(this, el);
this.currentElement = el;
this.locator && position(this.locator,el)
for (var i = 0 ; i < len; i++) {
var namespaceURI = attrs.getURI(i);
var value = attrs.getValue(i);
var qName = attrs.getQName(i);
var attr = doc.createAttributeNS(namespaceURI, qName);
this.locator &&position(attrs.getLocator(i),attr);
attr.value = attr.nodeValue = value;
el.setAttributeNode(attr)
}
},
endElement:function(namespaceURI, localName, qName) {
var current = this.currentElement
var tagName = current.tagName;
this.currentElement = current.parentNode;
},
startPrefixMapping:function(prefix, uri) {
},
endPrefixMapping:function(prefix) {
},
processingInstruction:function(target, data) {
var ins = this.doc.createProcessingInstruction(target, data);
this.locator && position(this.locator,ins)
appendElement(this, ins);
},
ignorableWhitespace:function(ch, start, length) {
},
characters:function(chars, start, length) {
chars = _toString.apply(this,arguments)
//console.log(chars)
if(chars){
if (this.cdata) {
var charNode = this.doc.createCDATASection(chars);
} else {
var charNode = this.doc.createTextNode(chars);
}
if(this.currentElement){
this.currentElement.appendChild(charNode);
}else if(/^\s*$/.test(chars)){
this.doc.appendChild(charNode);
//process xml
}
this.locator && position(this.locator,charNode)
}
},
skippedEntity:function(name) {
},
endDocument:function() {
this.doc.normalize();
},
setDocumentLocator:function (locator) {
if(this.locator = locator){// && !('lineNumber' in locator)){
locator.lineNumber = 0;
}
},
//LexicalHandler
comment:function(chars, start, length) {
chars = _toString.apply(this,arguments)
var comm = this.doc.createComment(chars);
this.locator && position(this.locator,comm)
appendElement(this, comm);
},
startCDATA:function() {
//used in characters() methods
this.cdata = true;
},
endCDATA:function() {
this.cdata = false;
},
startDTD:function(name, publicId, systemId) {
var impl = this.doc.implementation;
if (impl && impl.createDocumentType) {
var dt = impl.createDocumentType(name, publicId, systemId);
this.locator && position(this.locator,dt)
appendElement(this, dt);
this.doc.doctype = dt;
}
},
/**
* @see org.xml.sax.ErrorHandler
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
*/
warning:function(error) {
console.warn('[xmldom warning]\t'+error,_locator(this.locator));
},
error:function(error) {
console.error('[xmldom error]\t'+error,_locator(this.locator));
},
fatalError:function(error) {
throw new ParseError(error, this.locator);
}
}
function _locator(l){
if(l){
return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']'
}
}
function _toString(chars,start,length){
if(typeof chars == 'string'){
return chars.substr(start,length)
}else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
if(chars.length >= start+length || start){
return new java.lang.String(chars,start,length)+'';
}
return chars;
}
}
/*
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
* used method of org.xml.sax.ext.LexicalHandler:
* #comment(chars, start, length)
* #startCDATA()
* #endCDATA()
* #startDTD(name, publicId, systemId)
*
*
* IGNORED method of org.xml.sax.ext.LexicalHandler:
* #endDTD()
* #startEntity(name)
* #endEntity(name)
*
*
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
* IGNORED method of org.xml.sax.ext.DeclHandler
* #attributeDecl(eName, aName, type, mode, value)
* #elementDecl(name, model)
* #externalEntityDecl(name, publicId, systemId)
* #internalEntityDecl(name, value)
* @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
* IGNORED method of org.xml.sax.EntityResolver2
* #resolveEntity(String name,String publicId,String baseURI,String systemId)
* #resolveEntity(publicId, systemId)
* #getExternalSubset(name, baseURI)
* @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
* IGNORED method of org.xml.sax.DTDHandler
* #notationDecl(name, publicId, systemId) {};
* #unparsedEntityDecl(name, publicId, systemId, notationName) {};
*/
"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){
DOMHandler.prototype[key] = function(){return null}
})
/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */
function appendElement (hander,node) {
if (!hander.currentElement) {
hander.doc.appendChild(node);
} else {
hander.currentElement.appendChild(node);
}
}//appendChild and setAttributeNS are preformance key
exports.__DOMHandler = DOMHandler;
exports.normalizeLineEndings = normalizeLineEndings;
exports.DOMParser = DOMParser;
+2266
View File
File diff suppressed because it is too large. Load diff
+2166
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -0,0 +1,4 @@
var dom = require('./dom')
exports.DOMImplementation = dom.DOMImplementation
exports.XMLSerializer = dom.XMLSerializer
exports.DOMParser = require('./dom-parser').DOMParser
+683
View File
@@ -0,0 +1,683 @@
var NAMESPACE = require("./conventions").NAMESPACE;
var tagNamePattern = require("./conventions").tagNamePattern;
//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
var S_TAG = 0;//tag name offerring
var S_ATTR = 1;//attr name offerring
var S_ATTR_SPACE=2;//attr name end and space offer
var S_EQ = 3;//=space?
var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only)
var S_ATTR_END = 5;//attr value end and no space(quot end)
var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer)
var S_TAG_CLOSE = 7;//closed el<el />
/**
* Creates an error that will not be caught by XMLReader aka the SAX parser.
*
* @param {string} message
* @param {any?} locator Optional, can provide details about the location in the source
* @constructor
*/
function ParseError(message, locator) {
this.message = message
this.locator = locator
if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError);
}
ParseError.prototype = new Error();
ParseError.prototype.name = ParseError.name
function XMLReader(){
}
XMLReader.prototype = {
parse:function(source,defaultNSMap,entityMap){
var domBuilder = this.domBuilder;
domBuilder.startDocument();
_copy(defaultNSMap ,defaultNSMap = {})
parse(source,defaultNSMap,entityMap,
domBuilder,this.errorHandler);
domBuilder.endDocument();
}
}
function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){
function fixedFromCharCode(code) {
// String.prototype.fromCharCode does not supports
// > 2 bytes unicode chars directly
if (code > 0xffff) {
code -= 0x10000;
var surrogate1 = 0xd800 + (code >> 10)
, surrogate2 = 0xdc00 + (code & 0x3ff);
return String.fromCharCode(surrogate1, surrogate2);
} else {
return String.fromCharCode(code);
}
}
function entityReplacer(a){
var k = a.slice(1,-1);
if (Object.hasOwnProperty.call(entityMap, k)) {
return entityMap[k];
}else if(k.charAt(0) === '#'){
return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x')))
}else{
errorHandler.error('entity not found:'+a);
return a;
}
}
function appendText(end){//has some bugs
if(end>start){
var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
locator&&position(start);
domBuilder.characters(xt,0,end-start);
start = end
}
}
function position(p,m){
while(p>=lineEnd && (m = linePattern.exec(source))){
lineStart = m.index;
lineEnd = lineStart + m[0].length;
locator.lineNumber++;
//console.log('line++:',locator,startPos,endPos)
}
locator.columnNumber = p-lineStart+1;
}
var lineStart = 0;
var lineEnd = 0;
var linePattern = /.*(?:\r\n?|\n)|.*$/g
var locator = domBuilder.locator;
var parseStack = [{currentNSMap:defaultNSMapCopy}]
var closeMap = {};
var start = 0;
while(true){
try{
var tagStart = source.indexOf('<',start);
if(tagStart<0){
if(!source.substr(start).match(/^\s*$/)){
var doc = domBuilder.doc;
var text = doc.createTextNode(source.substr(start));
doc.appendChild(text);
domBuilder.currentElement = text;
}
return;
}
if(tagStart>start){
appendText(tagStart);
}
switch(source.charAt(tagStart+1)){
case '/':
var end = source.indexOf('>',tagStart+3);
// Anchored trailing-whitespace trim. The former `/[ \t\n\r]+$/g` backtracked
// quadratically on a name shaped `whitespace-run + non-whitespace char`
// (an end tag `</ … x>`): from every start position it extended `[ws]+`
// to the end and then failed the `$` anchor. This anchored form matches the
// whole string once and captures everything up to the last non-whitespace
// character, so it trims in linear time with byte-identical output.
var tagName = source.substring(tagStart + 2, end).replace(/^([\s\S]*?[^ \t\n\r])?[ \t\n\r]*$/, '$1');
var config = parseStack.pop();
if(end<0){
tagName = source.substring(tagStart+2).replace(/[\s<].*/,'');
errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName);
end = tagStart+1+tagName.length;
}else if(tagName.match(/\s</)){
tagName = tagName.replace(/[\s<].*/,'');
errorHandler.error("end tag name: "+tagName+' maybe not complete');
end = tagStart+1+tagName.length;
}else if(/[ \t\n\r]/.test(tagName) && tagNamePattern.test(tagName.split(/[ \t\n\r]/)[0])){
// The XML `ETag` production is `'</' Name S? '>'`: only optional whitespace may follow
// the `Name`. A valid `Name` followed by whitespace and non-whitespace residue (e.g.
// `</a\nbogus>` or `</a bogus>`) is not well-formed, but historically it was silently
// accepted (the malformed end tag ignored, the residue dropped, the element left on the
// stack). Report it as a recoverable `error` (which a custom errorHandler may escalate to
// fatal) while keeping the existing recovery so the parsed DOM stays byte-identical.
errorHandler.error('end tag name is followed by whitespace and trailing content: "'+tagName+'"');
}
var localNSMap = config.localNSMap;
var endMatch = config.tagName == tagName;
var endIgnoreCaseMach = endMatch || config.tagName&&config.tagName.toLowerCase() == tagName.toLowerCase()
if(endIgnoreCaseMach){
domBuilder.endElement(config.uri,config.localName,tagName);
if(localNSMap){
for (var prefix in localNSMap) {
if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
domBuilder.endPrefixMapping(prefix);
}
}
}
if(!endMatch){
errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); // No known test case
}
}else{
parseStack.push(config)
}
end++;
break;
// end elment
case '?':// <?...?>
locator&&position(tagStart);
end = parseInstruction(source,tagStart,domBuilder);
break;
case '!':// <!doctype,<![CDATA,<!--
locator&&position(tagStart);
end = parseDCC(source,tagStart,domBuilder,errorHandler);
break;
default:
locator&&position(tagStart);
var el = new ElementAttributes();
var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
//elStartEnd
var end = parseElementStartPart(source,tagStart,el,currentNSMap,entityReplacer,errorHandler);
var len = el.length;
if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
el.closed = true;
if(!entityMap.nbsp){
errorHandler.warning('unclosed xml attribute');
}
}
if(locator && len){
var locator2 = copyLocator(locator,{});
//try{//attribute position fixed
for(var i = 0;i<len;i++){
var a = el[i];
position(a.offset);
a.locator = copyLocator(locator,{});
}
domBuilder.locator = locator2
if(appendElement(el,domBuilder,currentNSMap)){
parseStack.push(el)
}
domBuilder.locator = locator;
}else{
if(appendElement(el,domBuilder,currentNSMap)){
parseStack.push(el)
}
}
if (NAMESPACE.isHTML(el.uri) && !el.closed) {
end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder)
} else {
end++;
}
}
}catch(e){
if (e instanceof ParseError) {
throw e;
}
errorHandler.error('element parse error: '+e)
end = -1;
}
if(end>start){
start = end;
}else{
//TODO: 这里有可能sax回退,有位置错误风险
appendText(Math.max(tagStart,start)+1);
}
}
}
function copyLocator(f,t){
t.lineNumber = f.lineNumber;
t.columnNumber = f.columnNumber;
return t;
}
/**
* @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
* @return end of the elementStartPart(end of elementEndPart for selfClosed el)
*/
function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){
/**
* @param {string} qname
* @param {string} value
* @param {number} startIndex
*/
function addAttribute(qname, value, startIndex) {
if (el.attributeNames.hasOwnProperty(qname)) {
errorHandler.fatalError('Attribute ' + qname + ' redefined')
}
el.addValue(
qname,
// @see https://www.w3.org/TR/xml/#AVNormalize
// since the xmldom sax parser does not "interpret" DTD the following is not implemented:
// - recursive replacement of (DTD) entity references
// - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer),
startIndex
)
}
var attrName;
var value;
var p = ++start;
var s = S_TAG;//status
while(true){
var c = source.charAt(p);
if (s === S_TAG && c === '<') {
// A `<` can never occur inside a tag name. Without this guard the scan runs
// on to the next `>` (or EOF) before the tag name is rejected, so a document
// with many `<` inside a malformed tag makes each one-character recovery step
// re-scan to the distant `>` — O(n^2). Stopping at the `<` keeps each recovery
// step bounded. The candidate scanned so far is reported raw, consistent with
// the sibling invalid-tag-name throw below.
throw new Error('unexpected < in tag name: ' + source.slice(start, p));
}
switch(c){
case '=':
if(s === S_ATTR){//attrName
attrName = source.slice(start,p);
s = S_EQ;
}else if(s === S_ATTR_SPACE){
s = S_EQ;
}else{
//fatalError: equal must after attrName or space after attrName
throw new Error('attribute equal must after attrName'); // No known test case
}
break;
case '\'':
case '"':
if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
){//equal
if(s === S_ATTR){
errorHandler.warning('attribute value must after "="')
attrName = source.slice(start,p)
}
start = p+1;
p = source.indexOf(c,start)
if(p>0){
value = source.slice(start, p);
addAttribute(attrName, value, start-1);
s = S_ATTR_END;
}else{
//fatalError: no end quot match
throw new Error('attribute value no end \''+c+'\' match');
}
}else if(s == S_ATTR_NOQUOT_VALUE){
value = source.slice(start, p);
addAttribute(attrName, value, start);
errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!');
start = p+1;
s = S_ATTR_END
}else{
//fatalError: no equal before
throw new Error('attribute value must after "="'); // No known test case
}
break;
case '/':
switch(s){
case S_TAG:
el.setTagName(source.slice(start,p));
case S_ATTR_END:
case S_TAG_SPACE:
case S_TAG_CLOSE:
s =S_TAG_CLOSE;
el.closed = true;
case S_ATTR_NOQUOT_VALUE:
case S_ATTR:
break;
case S_ATTR_SPACE:
el.closed = true;
break;
//case S_EQ:
default:
throw new Error("attribute invalid close char('/')") // No known test case
}
break;
case ''://end document
errorHandler.error('unexpected end of input');
if(s == S_TAG){
el.setTagName(source.slice(start,p));
}
return p;
case '>':
switch(s){
case S_TAG:
el.setTagName(source.slice(start,p));
case S_ATTR_END:
case S_TAG_SPACE:
case S_TAG_CLOSE:
break;//normal
case S_ATTR_NOQUOT_VALUE://Compatible state
case S_ATTR:
value = source.slice(start,p);
if(value.slice(-1) === '/'){
el.closed = true;
value = value.slice(0,-1)
}
case S_ATTR_SPACE:
if(s === S_ATTR_SPACE){
value = attrName;
}
if(s == S_ATTR_NOQUOT_VALUE){
errorHandler.warning('attribute "'+value+'" missed quot(")!');
addAttribute(attrName, value, start)
}else{
if(!NAMESPACE.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)){
errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!')
}
addAttribute(value, value, start)
}
break;
case S_EQ:
throw new Error('attribute value missed!!');
}
// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
return p;
/*xml space '\x20' | #x9 | #xD | #xA; */
case '\u0080':
c = ' ';
default:
if(c<= ' '){//space
switch(s){
case S_TAG:
el.setTagName(source.slice(start,p));//tagName
s = S_TAG_SPACE;
break;
case S_ATTR:
attrName = source.slice(start,p)
s = S_ATTR_SPACE;
break;
case S_ATTR_NOQUOT_VALUE:
var value = source.slice(start, p);
errorHandler.warning('attribute "'+value+'" missed quot(")!!');
addAttribute(attrName, value, start)
case S_ATTR_END:
s = S_TAG_SPACE;
break;
//case S_TAG_SPACE:
//case S_EQ:
//case S_ATTR_SPACE:
// void();break;
//case S_TAG_CLOSE:
//ignore warning
}
}else{//not space
//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
switch(s){
//case S_TAG:void();break;
//case S_ATTR:void();break;
//case S_ATTR_NOQUOT_VALUE:void();break;
case S_ATTR_SPACE:
var tagName = el.tagName;
if (!NAMESPACE.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!')
}
addAttribute(attrName, attrName, start);
start = p;
s = S_ATTR;
break;
case S_ATTR_END:
errorHandler.warning('attribute space is required"'+attrName+'"!!')
case S_TAG_SPACE:
s = S_ATTR;
start = p;
break;
case S_EQ:
s = S_ATTR_NOQUOT_VALUE;
start = p;
break;
case S_TAG_CLOSE:
throw new Error("elements closed character '/' and '>' must be connected to");
}
}
}//end outer switch
//console.log('p++',p)
p++;
}
}
/**
* @return true if has new namespace define
*/
function appendElement(el,domBuilder,currentNSMap){
var tagName = el.tagName;
var localNSMap = null;
//var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
var i = el.length;
while(i--){
var a = el[i];
var qName = a.qName;
var value = a.value;
var nsp = qName.indexOf(':');
if(nsp>0){
var prefix = a.prefix = qName.slice(0,nsp);
var localName = qName.slice(nsp+1);
var nsPrefix = prefix === 'xmlns' && localName
}else{
localName = qName;
prefix = null
nsPrefix = qName === 'xmlns' && ''
}
//can not set prefix,because prefix !== ''
a.localName = localName ;
//prefix == null for no ns prefix attribute
if(nsPrefix !== false){//hack!!
if(localNSMap == null){
localNSMap = {}
// Derive the child scope's namespace map by prototype-chain inheritance
// instead of a flat copy: lookups inherit ancestor prefixes transparently,
// so a document nesting N scopes retains O(N) map entries rather than
// sum(1..N) = O(N^2). localNSMap stays a flat own-only record of the
// prefixes declared at THIS element, so own-property enumeration
// (endPrefixMapping below) still reports only local declarations.
currentNSMap = Object.create(currentNSMap)
}
currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
a.uri = NAMESPACE.XMLNS
domBuilder.startPrefixMapping(nsPrefix, value)
}
}
var i = el.length;
while(i--){
a = el[i];
var prefix = a.prefix;
if(prefix){//no prefix attribute has no namespace
if(prefix === 'xml'){
a.uri = NAMESPACE.XML;
}if(prefix !== 'xmlns'){
a.uri = currentNSMap[prefix || '']
//{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
}
}
}
var nsp = tagName.indexOf(':');
if(nsp>0){
prefix = el.prefix = tagName.slice(0,nsp);
localName = el.localName = tagName.slice(nsp+1);
}else{
prefix = null;//important!!
localName = el.localName = tagName;
}
//no prefix element has default namespace
var ns = el.uri = currentNSMap[prefix || ''];
domBuilder.startElement(ns,localName,tagName,el);
//endPrefixMapping and startPrefixMapping have not any help for dom builder
//localNSMap = null
if(el.closed){
domBuilder.endElement(ns,localName,tagName);
if(localNSMap){
for (prefix in localNSMap) {
if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
domBuilder.endPrefixMapping(prefix);
}
}
}
}else{
el.currentNSMap = currentNSMap;
el.localNSMap = localNSMap;
//parseStack.push(el);
return true;
}
}
function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){
if(/^(?:script|textarea)$/i.test(tagName)){
var elEndStart = source.indexOf('</'+tagName+'>',elStartEnd);
var text = source.substring(elStartEnd+1,elEndStart);
if(/[&<]/.test(text)){
if(/^script$/i.test(tagName)){
//if(!/\]\]>/.test(text)){
//lexHandler.startCDATA();
domBuilder.characters(text,0,text.length);
//lexHandler.endCDATA();
return elEndStart;
//}
}//}else{//text area
text = text.replace(/&#?\w+;/g,entityReplacer);
domBuilder.characters(text,0,text.length);
return elEndStart;
//}
}
}
return elStartEnd+1;
}
function fixSelfClosed(source,elStartEnd,tagName,closeMap){
//if(tagName in closeMap){
var pos = closeMap[tagName];
if(pos == null){
//console.log(tagName)
pos = source.lastIndexOf('</'+tagName+'>')
if(pos<elStartEnd){//忘记闭合
pos = source.lastIndexOf('</'+tagName)
}
closeMap[tagName] =pos
}
return pos<elStartEnd;
//}
}
function _copy (source, target) {
for (var n in source) {
if (Object.prototype.hasOwnProperty.call(source, n)) {
target[n] = source[n];
}
}
}
function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
var next= source.charAt(start+2)
switch(next){
case '-':
if(source.charAt(start + 3) === '-'){
var end = source.indexOf('-->',start+4);
//append comment source.substring(4,end)//<!--
if(end>start){
domBuilder.comment(source,start+4,end-start-4);
return end+3;
}else{
errorHandler.error("Unclosed comment");
return -1;
}
}else{
//error
return -1;
}
default:
if(source.substr(start+3,6) == 'CDATA['){
var end = source.indexOf(']]>',start+9);
domBuilder.startCDATA();
domBuilder.characters(source,start+9,end-start-9);
domBuilder.endCDATA()
return end+3;
}
//<!DOCTYPE
//startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
var matchs = split(source,start);
var len = matchs.length;
if(len>1 && /!doctype/i.test(matchs[0][0])){
var name = matchs[1][0];
var pubid = false;
var sysid = false;
if(len>3){
if(/^public$/i.test(matchs[2][0])){
pubid = matchs[3][0];
sysid = len>4 && matchs[4][0];
}else if(/^system$/i.test(matchs[2][0])){
sysid = matchs[3][0];
}
}
var lastMatch = matchs[len-1]
domBuilder.startDTD(name, pubid, sysid);
domBuilder.endDTD();
return lastMatch.index+lastMatch[0].length
}
}
return -1;
}
function parseInstruction(source,start,domBuilder){
var end = source.indexOf('?>',start);
if(end){
var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)$/);
if(match){
var len = match[0].length;
domBuilder.processingInstruction(match[1], match[2]) ;
return end+2;
}else{//error
return -1;
}
}
return -1;
}
function ElementAttributes(){
this.attributeNames = {}
}
ElementAttributes.prototype = {
setTagName:function(tagName){
if(!tagNamePattern.test(tagName)){
throw new Error('invalid tagName:'+tagName)
}
this.tagName = tagName
},
addValue:function(qName, value, offset) {
if(!tagNamePattern.test(qName)){
throw new Error('invalid attribute:'+qName)
}
this.attributeNames[qName] = this.length;
this[this.length++] = {qName:qName,value:value,offset:offset}
},
length:0,
getLocalName:function(i){return this[i].localName},
getLocator:function(i){return this[i].locator},
getQName:function(i){return this[i].qName},
getURI:function(i){return this[i].uri},
getValue:function(i){return this[i].value}
// ,getIndex:function(uri, localName)){
// if(localName){
//
// }else{
// var qName = uri
// }
// },
// getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
// getType:function(uri,localName){}
// getType:function(i){},
}
function split(source,start){
var match;
var buf = [];
var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
reg.lastIndex = start;
reg.exec(source);//skip <
while(match = reg.exec(source)){
buf.push(match);
if(match[1])return buf;
}
}
exports.XMLReader = XMLReader;
exports.ParseError = ParseError;
+75
View File
@@ -0,0 +1,75 @@
{
"name": "@xmldom/xmldom",
"version": "0.8.15",
"description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.",
"keywords": [
"w3c",
"dom",
"xml",
"parser",
"javascript",
"DOMParser",
"XMLSerializer",
"ponyfill"
],
"homepage": "https://github.com/xmldom/xmldom",
"repository": {
"type": "git",
"url": "git://github.com/xmldom/xmldom.git"
},
"main": "lib/index.js",
"types": "index.d.ts",
"files": [
"CHANGELOG.md",
"LICENSE",
"readme.md",
"SECURITY.md",
"index.d.ts",
"lib"
],
"config": {
"test_stack_size": 256
},
"scripts": {
"lint": "eslint lib test",
"format": "prettier --write test",
"format:check": "prettier --check test",
"changelog": "auto-changelog --unreleased-only",
"start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'",
"stryker": "stryker run",
"stryker:dry-run": "stryker run -m '' --reporters progress",
"test": "node --stack-size=$npm_package_config_test_stack_size ./node_modules/.bin/jest",
"testrelease": "npm test && eslint lib",
"version": "./changelog-has-version.sh",
"release": "np --no-yarn --test-script testrelease --branch release-0.8.x patch"
},
"engines": {
"node": ">=10.0.0"
},
"dependencies": {},
"devDependencies": {
"@stryker-mutator/core": "5.6.1",
"auto-changelog": "2.4.0",
"eslint": "8.25.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-es5": "1.5.0",
"eslint-plugin-prettier": "4.2.1",
"get-stream": "6.0.1",
"jest": "27.5.1",
"nodemon": "2.0.20",
"np": "9.2.0",
"prettier": "2.7.1",
"xmltest": "1.5.0",
"yauzl": "2.10.0"
},
"bugs": {
"url": "https://github.com/xmldom/xmldom/issues"
},
"license": "MIT",
"auto-changelog": {
"prepend": true,
"remote": "upstream",
"tagPrefix": "",
"template": "./auto-changelog.hbs"
}
}
+356
View File
@@ -0,0 +1,356 @@
# @xmldom/xmldom
***Since version 0.7.0 this package is published to npm as [`@xmldom/xmldom`](https://www.npmjs.com/package/@xmldom/xmldom) and no longer as [`xmldom`](https://www.npmjs.com/package/xmldom), because [we are no longer able to publish `xmldom`](https://github.com/xmldom/xmldom/issues/271).***
*For better readability in the docs we will continue to talk about this library as "xmldom".*
[![license(MIT)](https://img.shields.io/npm/l/@xmldom/xmldom?color=blue&style=flat-square)](https://github.com/xmldom/xmldom/blob/master/LICENSE)
[![npm](https://img.shields.io/npm/v/@xmldom/xmldom?style=flat-square)](https://www.npmjs.com/package/@xmldom/xmldom)
[![snyk.io package health](https://snyk.io/advisor/npm-package/@xmldom/xmldom/badge.svg)](https://snyk.io/advisor/npm-package/@xmldom/xmldom)
[![bug issues](https://img.shields.io/github/issues/xmldom/xmldom/bug?color=red&style=flat-square)](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
[![help-wanted issues](https://img.shields.io/github/issues/xmldom/xmldom/help-wanted?color=darkgreen&style=flat-square)](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted)
[![Mutation report](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fxmldom%2Fxmldom%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/xmldom/xmldom/master)
xmldom is a javascript [ponyfill](https://ponyfill.com/) to provide the following APIs [that are present in modern browsers](https://caniuse.com/xml-serializer) to other runtimes:
- convert an XML string into a DOM tree
```
new DOMParser().parseFromString(xml, mimeType) => Document
```
- create, access and modify a DOM tree
```
new DOMImplementation().createDocument(...) => Document
```
- serialize a DOM tree back into an XML string
```
new XMLSerializer().serializeToString(node) => string
```
The target runtimes `xmldom` supports are currently Node >= v10 (ES5) and Rhino ([not tested as part of CI](https://github.com/xmldom/xmldom/discussions/214)).
When deciding how to fix bugs or implement features, `xmldom` tries to stay as close as possible to the various [related specifications/standards](#specs).
As indicated by the version starting with `0.`, this implementation is not feature complete and some implemented features differ from what the specifications describe.
**Issues and PRs for such differences are always welcome, even when they only provide a failing test case.**
This project was forked from it's [original source](https://github.com/jindw/xmldom) in 2019, more details about that transition can be found in the [CHANGELOG](CHANGELOG.md#maintainer-changes).
## Usage
### Install:
> npm install @xmldom/xmldom
### Example:
[In NodeJS](examples/nodejs/src/index.js)
```javascript
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom')
const source = `<xml xmlns="a">
<child>test</child>
<child/>
</xml>`
const doc = new DOMParser().parseFromString(source, 'text/xml')
const serialized = new XMLSerializer().serializeToString(doc)
```
Note: in Typescript ~and ES6~(see #316) you can use the `import` approach, as follows:
```typescript
import { DOMParser } from '@xmldom/xmldom'
```
## API Reference
* [DOMParser](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser):
```javascript
parseFromString(xmlsource,mimeType)
```
* **options extension** _by xmldom_ (not DOM standard!!)
```javascript
//added the options argument
new DOMParser(options)
//errorHandler is supported
new DOMParser({
/**
* locator is always need for error position info
*/
locator:{},
/**
* you can override the errorHandler for xml parser
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
*/
errorHandler:{warning:function(w){console.warn(w)},error:callback,fatalError:callback}
//only callback model
//errorHandler:function(level,msg){console.log(level,msg)}
})
```
* [XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer)
```javascript
serializeToString(node)
```
### DOM level2 method and attribute:
* [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247)
readonly class properties (aka `NodeType`),
these can be accessed from any `Node` instance `node`:
`if (node.nodeType === node.ELEMENT_NODE) {...`
1. `ELEMENT_NODE` (`1`)
2. `ATTRIBUTE_NODE` (`2`)
3. `TEXT_NODE` (`3`)
4. `CDATA_SECTION_NODE` (`4`)
5. `ENTITY_REFERENCE_NODE` (`5`)
6. `ENTITY_NODE` (`6`)
7. `PROCESSING_INSTRUCTION_NODE` (`7`)
8. `COMMENT_NODE` (`8`)
9. `DOCUMENT_NODE` (`9`)
10. `DOCUMENT_TYPE_NODE` (`10`)
11. `DOCUMENT_FRAGMENT_NODE` (`11`)
12. `NOTATION_NODE` (`12`)
attribute:
- `nodeValue` | `prefix`
readonly attribute:
- `nodeName` | `nodeType` | `parentNode` | `childNodes` | `firstChild` | `lastChild` | `previousSibling` | `nextSibling` | `attributes` | `ownerDocument` | `namespaceURI` | `localName`
method:
* `insertBefore(newChild, refChild)`
* `replaceChild(newChild, oldChild)`
* `removeChild(oldChild)`
* `appendChild(newChild)`
* `hasChildNodes()`
* `cloneNode(deep)`
* `normalize()`
* `isSupported(feature, version)`
* `hasAttributes()`
* [DOMException](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html)
extends the Error type thrown as part of DOM API.
readonly class properties:
- `INDEX_SIZE_ERR` (`1`)
- `DOMSTRING_SIZE_ERR` (`2`)
- `HIERARCHY_REQUEST_ERR` (`3`)
- `WRONG_DOCUMENT_ERR` (`4`)
- `INVALID_CHARACTER_ERR` (`5`)
- `NO_DATA_ALLOWED_ERR` (`6`)
- `NO_MODIFICATION_ALLOWED_ERR` (`7`)
- `NOT_FOUND_ERR` (`8`)
- `NOT_SUPPORTED_ERR` (`9`)
- `INUSE_ATTRIBUTE_ERR` (`10`)
- `INVALID_STATE_ERR` (`11`)
- `SYNTAX_ERR` (`12`)
- `INVALID_MODIFICATION_ERR` (`13`)
- `NAMESPACE_ERR` (`14`)
- `INVALID_ACCESS_ERR` (`15`)
attributes:
- `code` with a value matching one of the above constants.
* [DOMImplementation](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-102161490)
method:
- `hasFeature(feature, version)`
- `createDocumentType(qualifiedName, publicId, systemId)`
- `createDocument(namespaceURI, qualifiedName, doctype)`
* [Document](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document) : Node
readonly attribute:
- `doctype` | `implementation` | `documentElement`
method:
- `createElement(tagName)`
- `createDocumentFragment()`
- `createTextNode(data)`
- `createComment(data)`
- `createCDATASection(data)`
- `createProcessingInstruction(target, data)`
- `createAttribute(name)`
- `createEntityReference(name)`
- `getElementsByTagName(tagname)`
- `importNode(importedNode, deep)`
- `createElementNS(namespaceURI, qualifiedName)`
- `createAttributeNS(namespaceURI, qualifiedName)`
- `getElementsByTagNameNS(namespaceURI, localName)`
- `getElementById(elementId)`
* [DocumentFragment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-B63ED1A3) : Node
* [Element](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-745549614) : Node
readonly attribute:
- `tagName`
method:
- `getAttribute(name)`
- `setAttribute(name, value)`
- `removeAttribute(name)`
- `getAttributeNode(name)`
- `setAttributeNode(newAttr)`
- `removeAttributeNode(oldAttr)`
- `getElementsByTagName(name)`
- `getAttributeNS(namespaceURI, localName)`
- `setAttributeNS(namespaceURI, qualifiedName, value)`
- `removeAttributeNS(namespaceURI, localName)`
- `getAttributeNodeNS(namespaceURI, localName)`
- `setAttributeNodeNS(newAttr)`
- `getElementsByTagNameNS(namespaceURI, localName)`
- `hasAttribute(name)`
- `hasAttributeNS(namespaceURI, localName)`
* [Attr](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-637646024) : Node
attribute:
- `value`
readonly attribute:
- `name` | `specified` | `ownerElement`
* [NodeList](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177)
readonly attribute:
- `length`
method:
- `item(index)`
* [NamedNodeMap](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1780488922)
readonly attribute:
- `length`
method:
- `getNamedItem(name)`
- `setNamedItem(arg)`
- `removeNamedItem(name)`
- `item(index)`
- `getNamedItemNS(namespaceURI, localName)`
- `setNamedItemNS(arg)`
- `removeNamedItemNS(namespaceURI, localName)`
* [CharacterData](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-FF21A306) : Node
method:
- `substringData(offset, count)`
- `appendData(arg)`
- `insertData(offset, arg)`
- `deleteData(offset, count)`
- `replaceData(offset, count, arg)`
* [Text](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1312295772) : CharacterData
method:
- `splitText(offset)`
* [CDATASection](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-667469212)
* [Comment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1728279322) : CharacterData
* [DocumentType](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-412266927)
readonly attribute:
- `name` | `entities` | `notations` | `publicId` | `systemId` | `internalSubset`
* Notation : Node
readonly attribute:
- `publicId` | `systemId`
* Entity : Node
readonly attribute:
- `publicId` | `systemId` | `notationName`
* EntityReference : Node
* ProcessingInstruction : Node
attribute:
- `data`
readonly attribute:
- `target`
### DOM level 3 support:
* [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent)
attribute:
- `textContent`
method:
- `isDefaultNamespace(namespaceURI)`
- `lookupNamespaceURI(prefix)`
### DOM extension by xmldom
* [Node] Source position extension;
attribute:
- `lineNumber` //number starting from `1`
- `columnNumber` //number starting from `1`
## Specs
The implementation is based on several specifications:
<!-- Should open in new tab and the links in the SVG should be clickable there! -->
<a href="https://raw.githubusercontent.com/xmldom/xmldom/master/docs/specs.svg" target="_blank" rel="noopener noreferrer nofollow" >![Overview of related specifications and their relations](docs/specs.svg)</a>
### DOM Parsing and Serialization
From the [W3C DOM Parsing and Serialization (WD 2016)](https://www.w3.org/TR/2016/WD-DOM-Parsing-20160517/) `xmldom` provides an implementation for the interfaces:
- `DOMParser`
- `XMLSerializer`
Note that there are some known deviations between this implementation and the W3 specifications.
Note: [The latest version of this spec](https://w3c.github.io/DOM-Parsing/) has the status "Editors Draft", since it is under active development. One major change is that [the definition of the `DOMParser` interface has been moved to the HTML spec](https://w3c.github.io/DOM-Parsing/#the-domparser-interface)
### DOM
The original author claims that xmldom implements [DOM Level 2] in a "fully compatible" way and some parts of [DOM Level 3], but there are not enough tests to prove this. Both Specifications are now superseded by the [DOM Level 4 aka Living standard] wich has a much broader scope than xmldom.
xmldom implements the following interfaces (most constructors are currently not exposed):
- `Attr`
- `CDATASection`
- `CharacterData`
- `Comment`
- `Document`
- `DocumentFragment`
- `DocumentType`
- `DOMException` (constructor exposed)
- `DOMImplementation` (constructor exposed)
- `Element`
- `Entity`
- `EntityReference`
- `LiveNodeList`
- `NamedNodeMap`
- `Node` (constructor exposed)
- `NodeList`
- `Notation`
- `ProcessingInstruction`
- `Text`
more details are available in the (incomplete) [API Reference](#api-reference) section.
### HTML
xmldom does not have any goal of supporting the full spec, but it has some capability to parse, report and serialize things differently when "detecting HTML" (by checking the default namespace).
There is an upcoming change to better align the implementation with the latest specs, related to <https://github.com/xmldom/xmldom/issues/203>.
### SAX, XML, XMLNS
xmldom has an own SAX parser implementation to do the actual parsing, which implements some interfaces in alignment with the Java interfaces SAX defines:
- `XMLReader`
- `DOMHandler`
There is an idea/proposal to make it possible to replace it with something else in <https://github.com/xmldom/xmldom/issues/55>
+185
View File
@@ -0,0 +1,185 @@
1.0.10 / 2018-02-15
------------------
- Use .concat instead of + for arrays, #122.
1.0.9 / 2016-09-29
------------------
- Rerelease after 1.0.8 - deps cleanup.
1.0.8 / 2016-09-29
------------------
- Maintenance (deps bump, fix node 6.5+ tests, coverage report).
1.0.7 / 2016-03-17
------------------
- Teach `addArgument` to accept string arg names. #97, @tomxtobin.
1.0.6 / 2016-02-06
------------------
- Maintenance: moved to eslint & updated CS.
1.0.5 / 2016-02-05
------------------
- Removed lodash dependency to significantly reduce install size.
Thanks to @mourner.
1.0.4 / 2016-01-17
------------------
- Maintenance: lodash update to 4.0.0.
1.0.3 / 2015-10-27
------------------
- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple.
1.0.2 / 2015-03-22
------------------
- Relaxed lodash version dependency.
1.0.1 / 2015-02-20
------------------
- Changed dependencies to be compatible with ancient nodejs.
1.0.0 / 2015-02-19
------------------
- Maintenance release.
- Replaced `underscore` with `lodash`.
- Bumped version to 1.0.0 to better reflect semver meaning.
- HISTORY.md -> CHANGELOG.md
0.1.16 / 2013-12-01
-------------------
- Maintenance release. Updated dependencies and docs.
0.1.15 / 2013-05-13
-------------------
- Fixed #55, @trebor89
0.1.14 / 2013-05-12
-------------------
- Fixed #62, @maxtaco
0.1.13 / 2013-04-08
-------------------
- Added `.npmignore` to reduce package size
0.1.12 / 2013-02-10
-------------------
- Fixed conflictHandler (#46), @hpaulj
0.1.11 / 2013-02-07
-------------------
- Multiple bugfixes, @hpaulj
- Added 70+ tests (ported from python), @hpaulj
- Added conflictHandler, @applepicke
- Added fromfilePrefixChar, @hpaulj
0.1.10 / 2012-12-30
-------------------
- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion)
support, thanks to @hpaulj
- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj
0.1.9 / 2012-12-27
------------------
- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj
- Fixed default value behavior with `*` positionals, thanks to @hpaulj
- Improve `getDefault()` behavior, thanks to @hpaulj
- Imrove negative argument parsing, thanks to @hpaulj
0.1.8 / 2012-12-01
------------------
- Fixed parser parents (issue #19), thanks to @hpaulj
- Fixed negative argument parse (issue #20), thanks to @hpaulj
0.1.7 / 2012-10-14
------------------
- Fixed 'choices' argument parse (issue #16)
- Fixed stderr output (issue #15)
0.1.6 / 2012-09-09
------------------
- Fixed check for conflict of options (thanks to @tomxtobin)
0.1.5 / 2012-09-03
------------------
- Fix parser #setDefaults method (thanks to @tomxtobin)
0.1.4 / 2012-07-30
------------------
- Fixed pseudo-argument support (thanks to @CGamesPlay)
- Fixed addHelp default (should be true), if not set (thanks to @benblank)
0.1.3 / 2012-06-27
------------------
- Fixed formatter api name: Formatter -> HelpFormatter
0.1.2 / 2012-05-29
------------------
- Added basic tests
- Removed excess whitespace in help
- Fixed error reporting, when parcer with subcommands
called with empty arguments
0.1.1 / 2012-05-23
------------------
- Fixed line wrapping in help formatter
- Added better error reporting on invalid arguments
0.1.0 / 2012-05-16
------------------
- First release.
+21
View File
@@ -0,0 +1,21 @@
(The MIT License)
Copyright (C) 2012 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+257
View File
@@ -0,0 +1,257 @@
argparse
========
[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse)
[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse)
CLI arguments parser for node.js. Javascript port of python's
[argparse](http://docs.python.org/dev/library/argparse.html) module
(original version 3.2). That's a full port, except some very rare options,
recorded in issue tracker.
**NB. Difference with original.**
- Method names changed to camelCase. See [generated docs](http://nodeca.github.com/argparse/).
- Use `defaultValue` instead of `default`.
- Use `argparse.Const.REMAINDER` instead of `argparse.REMAINDER`, and
similarly for constant values `OPTIONAL`, `ZERO_OR_MORE`, and `ONE_OR_MORE`
(aliases for `nargs` values `'?'`, `'*'`, `'+'`, respectively), and
`SUPPRESS`.
Example
=======
test.js file:
```javascript
#!/usr/bin/env node
'use strict';
var ArgumentParser = require('../lib/argparse').ArgumentParser;
var parser = new ArgumentParser({
version: '0.0.1',
addHelp:true,
description: 'Argparse example'
});
parser.addArgument(
[ '-f', '--foo' ],
{
help: 'foo bar'
}
);
parser.addArgument(
[ '-b', '--bar' ],
{
help: 'bar foo'
}
);
parser.addArgument(
'--baz',
{
help: 'baz bar'
}
);
var args = parser.parseArgs();
console.dir(args);
```
Display help:
```
$ ./test.js -h
usage: example.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ]
Argparse example
Optional arguments:
-h, --help Show this help message and exit.
-v, --version Show program's version number and exit.
-f FOO, --foo FOO foo bar
-b BAR, --bar BAR bar foo
--baz BAZ baz bar
```
Parse arguments:
```
$ ./test.js -f=3 --bar=4 --baz 5
{ foo: '3', bar: '4', baz: '5' }
```
More [examples](https://github.com/nodeca/argparse/tree/master/examples).
ArgumentParser objects
======================
```
new ArgumentParser({parameters hash});
```
Creates a new ArgumentParser object.
**Supported params:**
- ```description``` - Text to display before the argument help.
- ```epilog``` - Text to display after the argument help.
- ```addHelp``` - Add a -h/–help option to the parser. (default: true)
- ```argumentDefault``` - Set the global default value for arguments. (default: null)
- ```parents``` - A list of ArgumentParser objects whose arguments should also be included.
- ```prefixChars``` - The set of characters that prefix optional arguments. (default: ‘-‘)
- ```formatterClass``` - A class for customizing the help output.
- ```prog``` - The name of the program (default: `path.basename(process.argv[1])`)
- ```usage``` - The string describing the program usage (default: generated)
- ```conflictHandler``` - Usually unnecessary, defines strategy for resolving conflicting optionals.
**Not supported yet**
- ```fromfilePrefixChars``` - The set of characters that prefix files from which additional arguments should be read.
Details in [original ArgumentParser guide](http://docs.python.org/dev/library/argparse.html#argumentparser-objects)
addArgument() method
====================
```
ArgumentParser.addArgument(name or flag or [name] or [flags...], {options})
```
Defines how a single command-line argument should be parsed.
- ```name or flag or [name] or [flags...]``` - Either a positional name
(e.g., `'foo'`), a single option (e.g., `'-f'` or `'--foo'`), an array
of a single positional name (e.g., `['foo']`), or an array of options
(e.g., `['-f', '--foo']`).
Options:
- ```action``` - The basic type of action to be taken when this argument is encountered at the command line.
- ```nargs```- The number of command-line arguments that should be consumed.
- ```constant``` - A constant value required by some action and nargs selections.
- ```defaultValue``` - The value produced if the argument is absent from the command line.
- ```type``` - The type to which the command-line argument should be converted.
- ```choices``` - A container of the allowable values for the argument.
- ```required``` - Whether or not the command-line option may be omitted (optionals only).
- ```help``` - A brief description of what the argument does.
- ```metavar``` - A name for the argument in usage messages.
- ```dest``` - The name of the attribute to be added to the object returned by parseArgs().
Details in [original add_argument guide](http://docs.python.org/dev/library/argparse.html#the-add-argument-method)
Action (some details)
================
ArgumentParser objects associate command-line arguments with actions.
These actions can do just about anything with the command-line arguments associated
with them, though most actions simply add an attribute to the object returned by
parseArgs(). The action keyword argument specifies how the command-line arguments
should be handled. The supported actions are:
- ```store``` - Just stores the argument’s value. This is the default action.
- ```storeConst``` - Stores value, specified by the const keyword argument.
(Note that the const keyword argument defaults to the rather unhelpful None.)
The 'storeConst' action is most commonly used with optional arguments, that
specify some sort of flag.
- ```storeTrue``` and ```storeFalse``` - Stores values True and False
respectively. These are special cases of 'storeConst'.
- ```append``` - Stores a list, and appends each argument value to the list.
This is useful to allow an option to be specified multiple times.
- ```appendConst``` - Stores a list, and appends value, specified by the
const keyword argument to the list. (Note, that the const keyword argument defaults
is None.) The 'appendConst' action is typically used when multiple arguments need
to store constants to the same list.
- ```count``` - Counts the number of times a keyword argument occurs. For example,
used for increasing verbosity levels.
- ```help``` - Prints a complete help message for all the options in the current
parser and then exits. By default a help action is automatically added to the parser.
See ArgumentParser for details of how the output is created.
- ```version``` - Prints version information and exit. Expects a `version=`
keyword argument in the addArgument() call.
Details in [original action guide](http://docs.python.org/dev/library/argparse.html#action)
Sub-commands
============
ArgumentParser.addSubparsers()
Many programs split their functionality into a number of sub-commands, for
example, the svn program can invoke sub-commands like `svn checkout`, `svn update`,
and `svn commit`. Splitting up functionality this way can be a particularly good
idea when a program performs several different functions which require different
kinds of command-line arguments. `ArgumentParser` supports creation of such
sub-commands with `addSubparsers()` method. The `addSubparsers()` method is
normally called with no arguments and returns an special action object.
This object has a single method `addParser()`, which takes a command name and
any `ArgumentParser` constructor arguments, and returns an `ArgumentParser` object
that can be modified as usual.
Example:
sub_commands.js
```javascript
#!/usr/bin/env node
'use strict';
var ArgumentParser = require('../lib/argparse').ArgumentParser;
var parser = new ArgumentParser({
version: '0.0.1',
addHelp:true,
description: 'Argparse examples: sub-commands',
});
var subparsers = parser.addSubparsers({
title:'subcommands',
dest:"subcommand_name"
});
var bar = subparsers.addParser('c1', {addHelp:true});
bar.addArgument(
[ '-f', '--foo' ],
{
action: 'store',
help: 'foo3 bar3'
}
);
var bar = subparsers.addParser(
'c2',
{aliases:['co'], addHelp:true}
);
bar.addArgument(
[ '-b', '--bar' ],
{
action: 'store',
type: 'int',
help: 'foo3 bar3'
}
);
var args = parser.parseArgs();
console.dir(args);
```
Details in [original sub-commands guide](http://docs.python.org/dev/library/argparse.html#sub-commands)
Contributors
============
- [Eugene Shkuropat](https://github.com/shkuropat)
- [Paul Jacobson](https://github.com/hpaulj)
[others](https://github.com/nodeca/argparse/graphs/contributors)
License
=======
Copyright (c) 2012 [Vitaly Puzrin](https://github.com/puzrin).
Released under the MIT license. See
[LICENSE](https://github.com/nodeca/argparse/blob/master/LICENSE) for details.
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib/argparse');
+146
View File
@@ -0,0 +1,146 @@
/**
* class Action
*
* Base class for all actions
* Do not call in your code, use this class only for inherits your own action
*
* Information about how to convert command line strings to Javascript objects.
* Action objects are used by an ArgumentParser to represent the information
* needed to parse a single argument from one or more strings from the command
* line. The keyword arguments to the Action constructor are also all attributes
* of Action instances.
*
* ##### Allowed keywords:
*
* - `store`
* - `storeConstant`
* - `storeTrue`
* - `storeFalse`
* - `append`
* - `appendConstant`
* - `count`
* - `help`
* - `version`
*
* Information about action options see [[Action.new]]
*
* See also [original guide](http://docs.python.org/dev/library/argparse.html#action)
*
**/
'use strict';
// Constants
var c = require('./const');
/**
* new Action(options)
*
* Base class for all actions. Used only for inherits
*
*
* ##### Options:
*
* - `optionStrings` A list of command-line option strings for the action.
* - `dest` Attribute to hold the created object(s)
* - `nargs` The number of command-line arguments that should be consumed.
* By default, one argument will be consumed and a single value will be
* produced.
* - `constant` Default value for an action with no value.
* - `defaultValue` The value to be produced if the option is not specified.
* - `type` Cast to 'string'|'int'|'float'|'complex'|function (string). If
* None, 'string'.
* - `choices` The choices available.
* - `required` True if the action must always be specified at the command
* line.
* - `help` The help describing the argument.
* - `metavar` The name to be used for the option's argument with the help
* string. If None, the 'dest' value will be used as the name.
*
* ##### nargs supported values:
*
* - `N` (an integer) consumes N arguments (and produces a list)
* - `?` consumes zero or one arguments
* - `*` consumes zero or more arguments (and produces a list)
* - `+` consumes one or more arguments (and produces a list)
*
* Note: that the difference between the default and nargs=1 is that with the
* default, a single value will be produced, while with nargs=1, a list
* containing a single value will be produced.
**/
var Action = module.exports = function Action(options) {
options = options || {};
this.optionStrings = options.optionStrings || [];
this.dest = options.dest;
this.nargs = typeof options.nargs !== 'undefined' ? options.nargs : null;
this.constant = typeof options.constant !== 'undefined' ? options.constant : null;
this.defaultValue = options.defaultValue;
this.type = typeof options.type !== 'undefined' ? options.type : null;
this.choices = typeof options.choices !== 'undefined' ? options.choices : null;
this.required = typeof options.required !== 'undefined' ? options.required : false;
this.help = typeof options.help !== 'undefined' ? options.help : null;
this.metavar = typeof options.metavar !== 'undefined' ? options.metavar : null;
if (!(this.optionStrings instanceof Array)) {
throw new Error('optionStrings should be an array');
}
if (typeof this.required !== 'undefined' && typeof this.required !== 'boolean') {
throw new Error('required should be a boolean');
}
};
/**
* Action#getName -> String
*
* Tells action name
**/
Action.prototype.getName = function () {
if (this.optionStrings.length > 0) {
return this.optionStrings.join('/');
} else if (this.metavar !== null && this.metavar !== c.SUPPRESS) {
return this.metavar;
} else if (typeof this.dest !== 'undefined' && this.dest !== c.SUPPRESS) {
return this.dest;
}
return null;
};
/**
* Action#isOptional -> Boolean
*
* Return true if optional
**/
Action.prototype.isOptional = function () {
return !this.isPositional();
};
/**
* Action#isPositional -> Boolean
*
* Return true if positional
**/
Action.prototype.isPositional = function () {
return (this.optionStrings.length === 0);
};
/**
* Action#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Should be implemented in inherited classes
*
* ##### Example
*
* ActionCount.prototype.call = function (parser, namespace, values, optionString) {
* namespace.set(this.dest, (namespace[this.dest] || 0) + 1);
* };
*
**/
Action.prototype.call = function () {
throw new Error('.call() not defined');// Not Implemented error
};
+53
View File
@@ -0,0 +1,53 @@
/*:nodoc:*
* class ActionAppend
*
* This action stores a list, and appends each argument value to the list.
* This is useful to allow an option to be specified multiple times.
* This class inherided from [[Action]]
*
**/
'use strict';
var util = require('util');
var Action = require('../action');
// Constants
var c = require('../const');
/*:nodoc:*
* new ActionAppend(options)
* - options (object): options hash see [[Action.new]]
*
* Note: options.nargs should be optional for constants
* and more then zero for other
**/
var ActionAppend = module.exports = function ActionAppend(options) {
options = options || {};
if (this.nargs <= 0) {
throw new Error('nargs for append actions must be > 0; if arg ' +
'strings are not supplying the value to append, ' +
'the append const action may be more appropriate');
}
if (!!this.constant && this.nargs !== c.OPTIONAL) {
throw new Error('nargs must be OPTIONAL to supply const');
}
Action.call(this, options);
};
util.inherits(ActionAppend, Action);
/*:nodoc:*
* ActionAppend#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Save result in namespace object
**/
ActionAppend.prototype.call = function (parser, namespace, values) {
var items = (namespace[this.dest] || []).slice();
items.push(values);
namespace.set(this.dest, items);
};
+47
View File
@@ -0,0 +1,47 @@
/*:nodoc:*
* class ActionAppendConstant
*
* This stores a list, and appends the value specified by
* the const keyword argument to the list.
* (Note that the const keyword argument defaults to null.)
* The 'appendConst' action is typically useful when multiple
* arguments need to store constants to the same list.
*
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var Action = require('../../action');
/*:nodoc:*
* new ActionAppendConstant(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionAppendConstant = module.exports = function ActionAppendConstant(options) {
options = options || {};
options.nargs = 0;
if (typeof options.constant === 'undefined') {
throw new Error('constant option is required for appendAction');
}
Action.call(this, options);
};
util.inherits(ActionAppendConstant, Action);
/*:nodoc:*
* ActionAppendConstant#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Save result in namespace object
**/
ActionAppendConstant.prototype.call = function (parser, namespace) {
var items = [].concat(namespace[this.dest] || []);
items.push(this.constant);
namespace.set(this.dest, items);
};
+40
View File
@@ -0,0 +1,40 @@
/*:nodoc:*
* class ActionCount
*
* This counts the number of times a keyword argument occurs.
* For example, this is useful for increasing verbosity levels
*
* This class inherided from [[Action]]
*
**/
'use strict';
var util = require('util');
var Action = require('../action');
/*:nodoc:*
* new ActionCount(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionCount = module.exports = function ActionCount(options) {
options = options || {};
options.nargs = 0;
Action.call(this, options);
};
util.inherits(ActionCount, Action);
/*:nodoc:*
* ActionCount#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Save result in namespace object
**/
ActionCount.prototype.call = function (parser, namespace) {
namespace.set(this.dest, (namespace[this.dest] || 0) + 1);
};
+47
View File
@@ -0,0 +1,47 @@
/*:nodoc:*
* class ActionHelp
*
* Support action for printing help
* This class inherided from [[Action]]
**/
'use strict';
var util = require('util');
var Action = require('../action');
// Constants
var c = require('../const');
/*:nodoc:*
* new ActionHelp(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionHelp = module.exports = function ActionHelp(options) {
options = options || {};
if (options.defaultValue !== null) {
options.defaultValue = options.defaultValue;
} else {
options.defaultValue = c.SUPPRESS;
}
options.dest = (options.dest !== null ? options.dest : c.SUPPRESS);
options.nargs = 0;
Action.call(this, options);
};
util.inherits(ActionHelp, Action);
/*:nodoc:*
* ActionHelp#call(parser, namespace, values, optionString)
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Print help and exit
**/
ActionHelp.prototype.call = function (parser) {
parser.printHelp();
parser.exit();
};
+50
View File
@@ -0,0 +1,50 @@
/*:nodoc:*
* class ActionStore
*
* This action just stores the argument’s value. This is the default action.
*
* This class inherited from [[Action]]
*
**/
'use strict';
var util = require('util');
var Action = require('../action');
// Constants
var c = require('../const');
/*:nodoc:*
* new ActionStore(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionStore = module.exports = function ActionStore(options) {
options = options || {};
if (this.nargs <= 0) {
throw new Error('nargs for store actions must be > 0; if you ' +
'have nothing to store, actions such as store ' +
'true or store const may be more appropriate');
}
if (typeof this.constant !== 'undefined' && this.nargs !== c.OPTIONAL) {
throw new Error('nargs must be OPTIONAL to supply const');
}
Action.call(this, options);
};
util.inherits(ActionStore, Action);
/*:nodoc:*
* ActionStore#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Save result in namespace object
**/
ActionStore.prototype.call = function (parser, namespace, values) {
namespace.set(this.dest, values);
};
+43
View File
@@ -0,0 +1,43 @@
/*:nodoc:*
* class ActionStoreConstant
*
* This action stores the value specified by the const keyword argument.
* (Note that the const keyword argument defaults to the rather unhelpful null.)
* The 'store_const' action is most commonly used with optional
* arguments that specify some sort of flag.
*
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var Action = require('../../action');
/*:nodoc:*
* new ActionStoreConstant(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionStoreConstant = module.exports = function ActionStoreConstant(options) {
options = options || {};
options.nargs = 0;
if (typeof options.constant === 'undefined') {
throw new Error('constant option is required for storeAction');
}
Action.call(this, options);
};
util.inherits(ActionStoreConstant, Action);
/*:nodoc:*
* ActionStoreConstant#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Save result in namespace object
**/
ActionStoreConstant.prototype.call = function (parser, namespace) {
namespace.set(this.dest, this.constant);
};
+27
View File
@@ -0,0 +1,27 @@
/*:nodoc:*
* class ActionStoreFalse
*
* This action store the values False respectively.
* This is special cases of 'storeConst'
*
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var ActionStoreConstant = require('./constant');
/*:nodoc:*
* new ActionStoreFalse(options)
* - options (object): hash of options see [[Action.new]]
*
**/
var ActionStoreFalse = module.exports = function ActionStoreFalse(options) {
options = options || {};
options.constant = false;
options.defaultValue = options.defaultValue !== null ? options.defaultValue : true;
ActionStoreConstant.call(this, options);
};
util.inherits(ActionStoreFalse, ActionStoreConstant);
+26
View File
@@ -0,0 +1,26 @@
/*:nodoc:*
* class ActionStoreTrue
*
* This action store the values True respectively.
* This isspecial cases of 'storeConst'
*
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var ActionStoreConstant = require('./constant');
/*:nodoc:*
* new ActionStoreTrue(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionStoreTrue = module.exports = function ActionStoreTrue(options) {
options = options || {};
options.constant = true;
options.defaultValue = options.defaultValue !== null ? options.defaultValue : false;
ActionStoreConstant.call(this, options);
};
util.inherits(ActionStoreTrue, ActionStoreConstant);
+149
View File
@@ -0,0 +1,149 @@
/** internal
* class ActionSubparsers
*
* Support the creation of such sub-commands with the addSubparsers()
*
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var format = require('util').format;
var Action = require('../action');
// Constants
var c = require('../const');
// Errors
var argumentErrorHelper = require('../argument/error');
/*:nodoc:*
* new ChoicesPseudoAction(name, help)
*
* Create pseudo action for correct help text
*
**/
function ChoicesPseudoAction(name, help) {
var options = {
optionStrings: [],
dest: name,
help: help
};
Action.call(this, options);
}
util.inherits(ChoicesPseudoAction, Action);
/**
* new ActionSubparsers(options)
* - options (object): options hash see [[Action.new]]
*
**/
function ActionSubparsers(options) {
options = options || {};
options.dest = options.dest || c.SUPPRESS;
options.nargs = c.PARSER;
this.debug = (options.debug === true);
this._progPrefix = options.prog;
this._parserClass = options.parserClass;
this._nameParserMap = {};
this._choicesActions = [];
options.choices = this._nameParserMap;
Action.call(this, options);
}
util.inherits(ActionSubparsers, Action);
/*:nodoc:*
* ActionSubparsers#addParser(name, options) -> ArgumentParser
* - name (string): sub-command name
* - options (object): see [[ArgumentParser.new]]
*
* Note:
* addParser supports an additional aliases option,
* which allows multiple strings to refer to the same subparser.
* This example, like svn, aliases co as a shorthand for checkout
*
**/
ActionSubparsers.prototype.addParser = function (name, options) {
var parser;
var self = this;
options = options || {};
options.debug = (this.debug === true);
// set program from the existing prefix
if (!options.prog) {
options.prog = this._progPrefix + ' ' + name;
}
var aliases = options.aliases || [];
// create a pseudo-action to hold the choice help
if (!!options.help || typeof options.help === 'string') {
var help = options.help;
delete options.help;
var choiceAction = new ChoicesPseudoAction(name, help);
this._choicesActions.push(choiceAction);
}
// create the parser and add it to the map
parser = new this._parserClass(options);
this._nameParserMap[name] = parser;
// make parser available under aliases also
aliases.forEach(function (alias) {
self._nameParserMap[alias] = parser;
});
return parser;
};
ActionSubparsers.prototype._getSubactions = function () {
return this._choicesActions;
};
/*:nodoc:*
* ActionSubparsers#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Call the action. Parse input aguments
**/
ActionSubparsers.prototype.call = function (parser, namespace, values) {
var parserName = values[0];
var argStrings = values.slice(1);
// set the parser name if requested
if (this.dest !== c.SUPPRESS) {
namespace[this.dest] = parserName;
}
// select the parser
if (this._nameParserMap[parserName]) {
parser = this._nameParserMap[parserName];
} else {
throw argumentErrorHelper(format(
'Unknown parser "%s" (choices: [%s]).',
parserName,
Object.keys(this._nameParserMap).join(', ')
));
}
// parse all the remaining options into the namespace
parser.parseArgs(argStrings, namespace);
};
module.exports = ActionSubparsers;
+47
View File
@@ -0,0 +1,47 @@
/*:nodoc:*
* class ActionVersion
*
* Support action for printing program version
* This class inherited from [[Action]]
**/
'use strict';
var util = require('util');
var Action = require('../action');
//
// Constants
//
var c = require('../const');
/*:nodoc:*
* new ActionVersion(options)
* - options (object): options hash see [[Action.new]]
*
**/
var ActionVersion = module.exports = function ActionVersion(options) {
options = options || {};
options.defaultValue = (options.defaultValue ? options.defaultValue : c.SUPPRESS);
options.dest = (options.dest || c.SUPPRESS);
options.nargs = 0;
this.version = options.version;
Action.call(this, options);
};
util.inherits(ActionVersion, Action);
/*:nodoc:*
* ActionVersion#call(parser, namespace, values, optionString) -> Void
* - parser (ArgumentParser): current parser
* - namespace (Namespace): namespace for output data
* - values (Array): parsed values
* - optionString (Array): input option string(not parsed)
*
* Print version and exit
**/
ActionVersion.prototype.call = function (parser) {
var version = this.version || parser.version;
var formatter = parser._getFormatter();
formatter.addText(version);
parser.exit(0, formatter.formatHelp());
};
+482
View File
@@ -0,0 +1,482 @@
/** internal
* class ActionContainer
*
* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]
**/
'use strict';
var format = require('util').format;
// Constants
var c = require('./const');
var $$ = require('./utils');
//Actions
var ActionHelp = require('./action/help');
var ActionAppend = require('./action/append');
var ActionAppendConstant = require('./action/append/constant');
var ActionCount = require('./action/count');
var ActionStore = require('./action/store');
var ActionStoreConstant = require('./action/store/constant');
var ActionStoreTrue = require('./action/store/true');
var ActionStoreFalse = require('./action/store/false');
var ActionVersion = require('./action/version');
var ActionSubparsers = require('./action/subparsers');
// Errors
var argumentErrorHelper = require('./argument/error');
/**
* new ActionContainer(options)
*
* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]
*
* ##### Options:
*
* - `description` -- A description of what the program does
* - `prefixChars` -- Characters that prefix optional arguments
* - `argumentDefault` -- The default value for all arguments
* - `conflictHandler` -- The conflict handler to use for duplicate arguments
**/
var ActionContainer = module.exports = function ActionContainer(options) {
options = options || {};
this.description = options.description;
this.argumentDefault = options.argumentDefault;
this.prefixChars = options.prefixChars || '';
this.conflictHandler = options.conflictHandler;
// set up registries
this._registries = {};
// register actions
this.register('action', null, ActionStore);
this.register('action', 'store', ActionStore);
this.register('action', 'storeConst', ActionStoreConstant);
this.register('action', 'storeTrue', ActionStoreTrue);
this.register('action', 'storeFalse', ActionStoreFalse);
this.register('action', 'append', ActionAppend);
this.register('action', 'appendConst', ActionAppendConstant);
this.register('action', 'count', ActionCount);
this.register('action', 'help', ActionHelp);
this.register('action', 'version', ActionVersion);
this.register('action', 'parsers', ActionSubparsers);
// raise an exception if the conflict handler is invalid
this._getHandler();
// action storage
this._actions = [];
this._optionStringActions = {};
// groups
this._actionGroups = [];
this._mutuallyExclusiveGroups = [];
// defaults storage
this._defaults = {};
// determines whether an "option" looks like a negative number
// -1, -1.5 -5e+4
this._regexpNegativeNumber = new RegExp('^[-]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$');
// whether or not there are any optionals that look like negative
// numbers -- uses a list so it can be shared and edited
this._hasNegativeNumberOptionals = [];
};
// Groups must be required, then ActionContainer already defined
var ArgumentGroup = require('./argument/group');
var MutuallyExclusiveGroup = require('./argument/exclusive');
//
// Registration methods
//
/**
* ActionContainer#register(registryName, value, object) -> Void
* - registryName (String) : object type action|type
* - value (string) : keyword
* - object (Object|Function) : handler
*
* Register handlers
**/
ActionContainer.prototype.register = function (registryName, value, object) {
this._registries[registryName] = this._registries[registryName] || {};
this._registries[registryName][value] = object;
};
ActionContainer.prototype._registryGet = function (registryName, value, defaultValue) {
if (arguments.length < 3) {
defaultValue = null;
}
return this._registries[registryName][value] || defaultValue;
};
//
// Namespace default accessor methods
//
/**
* ActionContainer#setDefaults(options) -> Void
* - options (object):hash of options see [[Action.new]]
*
* Set defaults
**/
ActionContainer.prototype.setDefaults = function (options) {
options = options || {};
for (var property in options) {
if ($$.has(options, property)) {
this._defaults[property] = options[property];
}
}
// if these defaults match any existing arguments, replace the previous
// default on the object with the new one
this._actions.forEach(function (action) {
if ($$.has(options, action.dest)) {
action.defaultValue = options[action.dest];
}
});
};
/**
* ActionContainer#getDefault(dest) -> Mixed
* - dest (string): action destination
*
* Return action default value
**/
ActionContainer.prototype.getDefault = function (dest) {
var result = $$.has(this._defaults, dest) ? this._defaults[dest] : null;
this._actions.forEach(function (action) {
if (action.dest === dest && $$.has(action, 'defaultValue')) {
result = action.defaultValue;
}
});
return result;
};
//
// Adding argument actions
//
/**
* ActionContainer#addArgument(args, options) -> Object
* - args (String|Array): argument key, or array of argument keys
* - options (Object): action objects see [[Action.new]]
*
* #### Examples
* - addArgument([ '-f', '--foo' ], { action: 'store', defaultValue: 1, ... })
* - addArgument([ 'bar' ], { action: 'store', nargs: 1, ... })
* - addArgument('--baz', { action: 'store', nargs: 1, ... })
**/
ActionContainer.prototype.addArgument = function (args, options) {
args = args;
options = options || {};
if (typeof args === 'string') {
args = [ args ];
}
if (!Array.isArray(args)) {
throw new TypeError('addArgument first argument should be a string or an array');
}
if (typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('addArgument second argument should be a hash');
}
// if no positional args are supplied or only one is supplied and
// it doesn't look like an option string, parse a positional argument
if (!args || args.length === 1 && this.prefixChars.indexOf(args[0][0]) < 0) {
if (args && !!options.dest) {
throw new Error('dest supplied twice for positional argument');
}
options = this._getPositional(args, options);
// otherwise, we're adding an optional argument
} else {
options = this._getOptional(args, options);
}
// if no default was supplied, use the parser-level default
if (typeof options.defaultValue === 'undefined') {
var dest = options.dest;
if ($$.has(this._defaults, dest)) {
options.defaultValue = this._defaults[dest];
} else if (typeof this.argumentDefault !== 'undefined') {
options.defaultValue = this.argumentDefault;
}
}
// create the action object, and add it to the parser
var ActionClass = this._popActionClass(options);
if (typeof ActionClass !== 'function') {
throw new Error(format('Unknown action "%s".', ActionClass));
}
var action = new ActionClass(options);
// throw an error if the action type is not callable
var typeFunction = this._registryGet('type', action.type, action.type);
if (typeof typeFunction !== 'function') {
throw new Error(format('"%s" is not callable', typeFunction));
}
return this._addAction(action);
};
/**
* ActionContainer#addArgumentGroup(options) -> ArgumentGroup
* - options (Object): hash of options see [[ArgumentGroup.new]]
*
* Create new arguments groups
**/
ActionContainer.prototype.addArgumentGroup = function (options) {
var group = new ArgumentGroup(this, options);
this._actionGroups.push(group);
return group;
};
/**
* ActionContainer#addMutuallyExclusiveGroup(options) -> ArgumentGroup
* - options (Object): {required: false}
*
* Create new mutual exclusive groups
**/
ActionContainer.prototype.addMutuallyExclusiveGroup = function (options) {
var group = new MutuallyExclusiveGroup(this, options);
this._mutuallyExclusiveGroups.push(group);
return group;
};
ActionContainer.prototype._addAction = function (action) {
var self = this;
// resolve any conflicts
this._checkConflict(action);
// add to actions list
this._actions.push(action);
action.container = this;
// index the action by any option strings it has
action.optionStrings.forEach(function (optionString) {
self._optionStringActions[optionString] = action;
});
// set the flag if any option strings look like negative numbers
action.optionStrings.forEach(function (optionString) {
if (optionString.match(self._regexpNegativeNumber)) {
if (!self._hasNegativeNumberOptionals.some(Boolean)) {
self._hasNegativeNumberOptionals.push(true);
}
}
});
// return the created action
return action;
};
ActionContainer.prototype._removeAction = function (action) {
var actionIndex = this._actions.indexOf(action);
if (actionIndex >= 0) {
this._actions.splice(actionIndex, 1);
}
};
ActionContainer.prototype._addContainerActions = function (container) {
// collect groups by titles
var titleGroupMap = {};
this._actionGroups.forEach(function (group) {
if (titleGroupMap[group.title]) {
throw new Error(format('Cannot merge actions - two groups are named "%s".', group.title));
}
titleGroupMap[group.title] = group;
});
// map each action to its group
var groupMap = {};
function actionHash(action) {
// unique (hopefully?) string suitable as dictionary key
return action.getName();
}
container._actionGroups.forEach(function (group) {
// if a group with the title exists, use that, otherwise
// create a new group matching the container's group
if (!titleGroupMap[group.title]) {
titleGroupMap[group.title] = this.addArgumentGroup({
title: group.title,
description: group.description
});
}
// map the actions to their new group
group._groupActions.forEach(function (action) {
groupMap[actionHash(action)] = titleGroupMap[group.title];
});
}, this);
// add container's mutually exclusive groups
// NOTE: if add_mutually_exclusive_group ever gains title= and
// description= then this code will need to be expanded as above
var mutexGroup;
container._mutuallyExclusiveGroups.forEach(function (group) {
mutexGroup = this.addMutuallyExclusiveGroup({
required: group.required
});
// map the actions to their new mutex group
group._groupActions.forEach(function (action) {
groupMap[actionHash(action)] = mutexGroup;
});
}, this); // forEach takes a 'this' argument
// add all actions to this container or their group
container._actions.forEach(function (action) {
var key = actionHash(action);
if (groupMap[key]) {
groupMap[key]._addAction(action);
} else {
this._addAction(action);
}
});
};
ActionContainer.prototype._getPositional = function (dest, options) {
if (Array.isArray(dest)) {
dest = dest[0];
}
// make sure required is not specified
if (options.required) {
throw new Error('"required" is an invalid argument for positionals.');
}
// mark positional arguments as required if at least one is
// always required
if (options.nargs !== c.OPTIONAL && options.nargs !== c.ZERO_OR_MORE) {
options.required = true;
}
if (options.nargs === c.ZERO_OR_MORE && typeof options.defaultValue === 'undefined') {
options.required = true;
}
// return the keyword arguments with no option strings
options.dest = dest;
options.optionStrings = [];
return options;
};
ActionContainer.prototype._getOptional = function (args, options) {
var prefixChars = this.prefixChars;
var optionStrings = [];
var optionStringsLong = [];
// determine short and long option strings
args.forEach(function (optionString) {
// error on strings that don't start with an appropriate prefix
if (prefixChars.indexOf(optionString[0]) < 0) {
throw new Error(format('Invalid option string "%s": must start with a "%s".',
optionString,
prefixChars
));
}
// strings starting with two prefix characters are long options
optionStrings.push(optionString);
if (optionString.length > 1 && prefixChars.indexOf(optionString[1]) >= 0) {
optionStringsLong.push(optionString);
}
});
// infer dest, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
var dest = options.dest || null;
delete options.dest;
if (!dest) {
var optionStringDest = optionStringsLong.length ? optionStringsLong[0] : optionStrings[0];
dest = $$.trimChars(optionStringDest, this.prefixChars);
if (dest.length === 0) {
throw new Error(
format('dest= is required for options like "%s"', optionStrings.join(', '))
);
}
dest = dest.replace(/-/g, '_');
}
// return the updated keyword arguments
options.dest = dest;
options.optionStrings = optionStrings;
return options;
};
ActionContainer.prototype._popActionClass = function (options, defaultValue) {
defaultValue = defaultValue || null;
var action = (options.action || defaultValue);
delete options.action;
var actionClass = this._registryGet('action', action, action);
return actionClass;
};
ActionContainer.prototype._getHandler = function () {
var handlerString = this.conflictHandler;
var handlerFuncName = '_handleConflict' + $$.capitalize(handlerString);
var func = this[handlerFuncName];
if (typeof func === 'undefined') {
var msg = 'invalid conflict resolution value: ' + handlerString;
throw new Error(msg);
} else {
return func;
}
};
ActionContainer.prototype._checkConflict = function (action) {
var optionStringActions = this._optionStringActions;
var conflictOptionals = [];
// find all options that conflict with this option
// collect pairs, the string, and an existing action that it conflicts with
action.optionStrings.forEach(function (optionString) {
var conflOptional = optionStringActions[optionString];
if (typeof conflOptional !== 'undefined') {
conflictOptionals.push([ optionString, conflOptional ]);
}
});
if (conflictOptionals.length > 0) {
var conflictHandler = this._getHandler();
conflictHandler.call(this, action, conflictOptionals);
}
};
ActionContainer.prototype._handleConflictError = function (action, conflOptionals) {
var conflicts = conflOptionals.map(function (pair) { return pair[0]; });
conflicts = conflicts.join(', ');
throw argumentErrorHelper(
action,
format('Conflicting option string(s): %s', conflicts)
);
};
ActionContainer.prototype._handleConflictResolve = function (action, conflOptionals) {
// remove all conflicting options
var self = this;
conflOptionals.forEach(function (pair) {
var optionString = pair[0];
var conflictingAction = pair[1];
// remove the conflicting option string
var i = conflictingAction.optionStrings.indexOf(optionString);
if (i >= 0) {
conflictingAction.optionStrings.splice(i, 1);
}
delete self._optionStringActions[optionString];
// if the option now has no option string, remove it from the
// container holding it
if (conflictingAction.optionStrings.length === 0) {
conflictingAction.container._removeAction(conflictingAction);
}
});
};
+14
View File
@@ -0,0 +1,14 @@
'use strict';
module.exports.ArgumentParser = require('./argument_parser.js');
module.exports.Namespace = require('./namespace');
module.exports.Action = require('./action');
module.exports.HelpFormatter = require('./help/formatter.js');
module.exports.Const = require('./const.js');
module.exports.ArgumentDefaultsHelpFormatter =
require('./help/added_formatters.js').ArgumentDefaultsHelpFormatter;
module.exports.RawDescriptionHelpFormatter =
require('./help/added_formatters.js').RawDescriptionHelpFormatter;
module.exports.RawTextHelpFormatter =
require('./help/added_formatters.js').RawTextHelpFormatter;
+50
View File
@@ -0,0 +1,50 @@
'use strict';
var format = require('util').format;
var ERR_CODE = 'ARGError';
/*:nodoc:*
* argumentError(argument, message) -> TypeError
* - argument (Object): action with broken argument
* - message (String): error message
*
* Error format helper. An error from creating or using an argument
* (optional or positional). The string value of this exception
* is the message, augmented with information
* about the argument that caused it.
*
* #####Example
*
* var argumentErrorHelper = require('./argument/error');
* if (conflictOptionals.length > 0) {
* throw argumentErrorHelper(
* action,
* format('Conflicting option string(s): %s', conflictOptionals.join(', '))
* );
* }
*
**/
module.exports = function (argument, message) {
var argumentName = null;
var errMessage;
var err;
if (argument.getName) {
argumentName = argument.getName();
} else {
argumentName = '' + argument;
}
if (!argumentName) {
errMessage = message;
} else {
errMessage = format('argument "%s": %s', argumentName, message);
}
err = new TypeError(errMessage);
err.code = ERR_CODE;
return err;
};
+54
View File
@@ -0,0 +1,54 @@
/** internal
* class MutuallyExclusiveGroup
*
* Group arguments.
* By default, ArgumentParser groups command-line arguments
* into “positional arguments” and “optional arguments”
* when displaying help messages. When there is a better
* conceptual grouping of arguments than this default one,
* appropriate groups can be created using the addArgumentGroup() method
*
* This class inherited from [[ArgumentContainer]]
**/
'use strict';
var util = require('util');
var ArgumentGroup = require('./group');
/**
* new MutuallyExclusiveGroup(container, options)
* - container (object): main container
* - options (object): options.required -> true/false
*
* `required` could be an argument itself, but making it a property of
* the options argument is more consistent with the JS adaptation of the Python)
**/
var MutuallyExclusiveGroup = module.exports = function MutuallyExclusiveGroup(container, options) {
var required;
options = options || {};
required = options.required || false;
ArgumentGroup.call(this, container);
this.required = required;
};
util.inherits(MutuallyExclusiveGroup, ArgumentGroup);
MutuallyExclusiveGroup.prototype._addAction = function (action) {
var msg;
if (action.required) {
msg = 'mutually exclusive arguments must be optional';
throw new Error(msg);
}
action = this._container._addAction(action);
this._groupActions.push(action);
return action;
};
MutuallyExclusiveGroup.prototype._removeAction = function (action) {
this._container._removeAction(action);
this._groupActions.remove(action);
};
+75
View File
@@ -0,0 +1,75 @@
/** internal
* class ArgumentGroup
*
* Group arguments.
* By default, ArgumentParser groups command-line arguments
* into “positional arguments” and “optional arguments”
* when displaying help messages. When there is a better
* conceptual grouping of arguments than this default one,
* appropriate groups can be created using the addArgumentGroup() method
*
* This class inherited from [[ArgumentContainer]]
**/
'use strict';
var util = require('util');
var ActionContainer = require('../action_container');
/**
* new ArgumentGroup(container, options)
* - container (object): main container
* - options (object): hash of group options
*
* #### options
* - **prefixChars** group name prefix
* - **argumentDefault** default argument value
* - **title** group title
* - **description** group description
*
**/
var ArgumentGroup = module.exports = function ArgumentGroup(container, options) {
options = options || {};
// add any missing keyword arguments by checking the container
options.conflictHandler = (options.conflictHandler || container.conflictHandler);
options.prefixChars = (options.prefixChars || container.prefixChars);
options.argumentDefault = (options.argumentDefault || container.argumentDefault);
ActionContainer.call(this, options);
// group attributes
this.title = options.title;
this._groupActions = [];
// share most attributes with the container
this._container = container;
this._registries = container._registries;
this._actions = container._actions;
this._optionStringActions = container._optionStringActions;
this._defaults = container._defaults;
this._hasNegativeNumberOptionals = container._hasNegativeNumberOptionals;
this._mutuallyExclusiveGroups = container._mutuallyExclusiveGroups;
};
util.inherits(ArgumentGroup, ActionContainer);
ArgumentGroup.prototype._addAction = function (action) {
// Parent add action
action = ActionContainer.prototype._addAction.call(this, action);
this._groupActions.push(action);
return action;
};
ArgumentGroup.prototype._removeAction = function (action) {
// Parent remove action
ActionContainer.prototype._removeAction.call(this, action);
var actionIndex = this._groupActions.indexOf(action);
if (actionIndex >= 0) {
this._groupActions.splice(actionIndex, 1);
}
};
+1161
View File
File diff suppressed because it is too large. Load diff
+21
View File
@@ -0,0 +1,21 @@
//
// Constants
//
'use strict';
module.exports.EOL = '\n';
module.exports.SUPPRESS = '==SUPPRESS==';
module.exports.OPTIONAL = '?';
module.exports.ZERO_OR_MORE = '*';
module.exports.ONE_OR_MORE = '+';
module.exports.PARSER = 'A...';
module.exports.REMAINDER = '...';
module.exports._UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args';
+87
View File
@@ -0,0 +1,87 @@
'use strict';
var util = require('util');
// Constants
var c = require('../const');
var $$ = require('../utils');
var HelpFormatter = require('./formatter.js');
/**
* new RawDescriptionHelpFormatter(options)
* new ArgumentParser({formatterClass: argparse.RawDescriptionHelpFormatter, ...})
*
* Help message formatter which adds default values to argument help.
*
* Only the name of this class is considered a public API. All the methods
* provided by the class are considered an implementation detail.
**/
function ArgumentDefaultsHelpFormatter(options) {
HelpFormatter.call(this, options);
}
util.inherits(ArgumentDefaultsHelpFormatter, HelpFormatter);
ArgumentDefaultsHelpFormatter.prototype._getHelpString = function (action) {
var help = action.help;
if (action.help.indexOf('%(defaultValue)s') === -1) {
if (action.defaultValue !== c.SUPPRESS) {
var defaulting_nargs = [ c.OPTIONAL, c.ZERO_OR_MORE ];
if (action.isOptional() || (defaulting_nargs.indexOf(action.nargs) >= 0)) {
help += ' (default: %(defaultValue)s)';
}
}
}
return help;
};
module.exports.ArgumentDefaultsHelpFormatter = ArgumentDefaultsHelpFormatter;
/**
* new RawDescriptionHelpFormatter(options)
* new ArgumentParser({formatterClass: argparse.RawDescriptionHelpFormatter, ...})
*
* Help message formatter which retains any formatting in descriptions.
*
* Only the name of this class is considered a public API. All the methods
* provided by the class are considered an implementation detail.
**/
function RawDescriptionHelpFormatter(options) {
HelpFormatter.call(this, options);
}
util.inherits(RawDescriptionHelpFormatter, HelpFormatter);
RawDescriptionHelpFormatter.prototype._fillText = function (text, width, indent) {
var lines = text.split('\n');
lines = lines.map(function (line) {
return $$.trimEnd(indent + line);
});
return lines.join('\n');
};
module.exports.RawDescriptionHelpFormatter = RawDescriptionHelpFormatter;
/**
* new RawTextHelpFormatter(options)
* new ArgumentParser({formatterClass: argparse.RawTextHelpFormatter, ...})
*
* Help message formatter which retains formatting of all help text.
*
* Only the name of this class is considered a public API. All the methods
* provided by the class are considered an implementation detail.
**/
function RawTextHelpFormatter(options) {
RawDescriptionHelpFormatter.call(this, options);
}
util.inherits(RawTextHelpFormatter, RawDescriptionHelpFormatter);
RawTextHelpFormatter.prototype._splitLines = function (text) {
return text.split('\n');
};
module.exports.RawTextHelpFormatter = RawTextHelpFormatter;
+795
View File
@@ -0,0 +1,795 @@
/**
* class HelpFormatter
*
* Formatter for generating usage messages and argument help strings. Only the
* name of this class is considered a public API. All the methods provided by
* the class are considered an implementation detail.
*
* Do not call in your code, use this class only for inherits your own forvatter
*
* ToDo add [additonal formatters][1]
*
* [1]:http://docs.python.org/dev/library/argparse.html#formatter-class
**/
'use strict';
var sprintf = require('sprintf-js').sprintf;
// Constants
var c = require('../const');
var $$ = require('../utils');
/*:nodoc:* internal
* new Support(parent, heding)
* - parent (object): parent section
* - heading (string): header string
*
**/
function Section(parent, heading) {
this._parent = parent;
this._heading = heading;
this._items = [];
}
/*:nodoc:* internal
* Section#addItem(callback) -> Void
* - callback (array): tuple with function and args
*
* Add function for single element
**/
Section.prototype.addItem = function (callback) {
this._items.push(callback);
};
/*:nodoc:* internal
* Section#formatHelp(formatter) -> string
* - formatter (HelpFormatter): current formatter
*
* Form help section string
*
**/
Section.prototype.formatHelp = function (formatter) {
var itemHelp, heading;
// format the indented section
if (this._parent) {
formatter._indent();
}
itemHelp = this._items.map(function (item) {
var obj, func, args;
obj = formatter;
func = item[0];
args = item[1];
return func.apply(obj, args);
});
itemHelp = formatter._joinParts(itemHelp);
if (this._parent) {
formatter._dedent();
}
// return nothing if the section was empty
if (!itemHelp) {
return '';
}
// add the heading if the section was non-empty
heading = '';
if (this._heading && this._heading !== c.SUPPRESS) {
var currentIndent = formatter.currentIndent;
heading = $$.repeat(' ', currentIndent) + this._heading + ':' + c.EOL;
}
// join the section-initialize newline, the heading and the help
return formatter._joinParts([ c.EOL, heading, itemHelp, c.EOL ]);
};
/**
* new HelpFormatter(options)
*
* #### Options:
* - `prog`: program name
* - `indentIncriment`: indent step, default value 2
* - `maxHelpPosition`: max help position, default value = 24
* - `width`: line width
*
**/
var HelpFormatter = module.exports = function HelpFormatter(options) {
options = options || {};
this._prog = options.prog;
this._maxHelpPosition = options.maxHelpPosition || 24;
this._width = (options.width || ((process.env.COLUMNS || 80) - 2));
this._currentIndent = 0;
this._indentIncriment = options.indentIncriment || 2;
this._level = 0;
this._actionMaxLength = 0;
this._rootSection = new Section(null);
this._currentSection = this._rootSection;
this._whitespaceMatcher = new RegExp('\\s+', 'g');
this._longBreakMatcher = new RegExp(c.EOL + c.EOL + c.EOL + '+', 'g');
};
HelpFormatter.prototype._indent = function () {
this._currentIndent += this._indentIncriment;
this._level += 1;
};
HelpFormatter.prototype._dedent = function () {
this._currentIndent -= this._indentIncriment;
this._level -= 1;
if (this._currentIndent < 0) {
throw new Error('Indent decreased below 0.');
}
};
HelpFormatter.prototype._addItem = function (func, args) {
this._currentSection.addItem([ func, args ]);
};
//
// Message building methods
//
/**
* HelpFormatter#startSection(heading) -> Void
* - heading (string): header string
*
* Start new help section
*
* See alse [code example][1]
*
* ##### Example
*
* formatter.startSection(actionGroup.title);
* formatter.addText(actionGroup.description);
* formatter.addArguments(actionGroup._groupActions);
* formatter.endSection();
*
**/
HelpFormatter.prototype.startSection = function (heading) {
this._indent();
var section = new Section(this._currentSection, heading);
var func = section.formatHelp.bind(section);
this._addItem(func, [ this ]);
this._currentSection = section;
};
/**
* HelpFormatter#endSection -> Void
*
* End help section
*
* ##### Example
*
* formatter.startSection(actionGroup.title);
* formatter.addText(actionGroup.description);
* formatter.addArguments(actionGroup._groupActions);
* formatter.endSection();
**/
HelpFormatter.prototype.endSection = function () {
this._currentSection = this._currentSection._parent;
this._dedent();
};
/**
* HelpFormatter#addText(text) -> Void
* - text (string): plain text
*
* Add plain text into current section
*
* ##### Example
*
* formatter.startSection(actionGroup.title);
* formatter.addText(actionGroup.description);
* formatter.addArguments(actionGroup._groupActions);
* formatter.endSection();
*
**/
HelpFormatter.prototype.addText = function (text) {
if (text && text !== c.SUPPRESS) {
this._addItem(this._formatText, [ text ]);
}
};
/**
* HelpFormatter#addUsage(usage, actions, groups, prefix) -> Void
* - usage (string): usage text
* - actions (array): actions list
* - groups (array): groups list
* - prefix (string): usage prefix
*
* Add usage data into current section
*
* ##### Example
*
* formatter.addUsage(this.usage, this._actions, []);
* return formatter.formatHelp();
*
**/
HelpFormatter.prototype.addUsage = function (usage, actions, groups, prefix) {
if (usage !== c.SUPPRESS) {
this._addItem(this._formatUsage, [ usage, actions, groups, prefix ]);
}
};
/**
* HelpFormatter#addArgument(action) -> Void
* - action (object): action
*
* Add argument into current section
*
* Single variant of [[HelpFormatter#addArguments]]
**/
HelpFormatter.prototype.addArgument = function (action) {
if (action.help !== c.SUPPRESS) {
var self = this;
// find all invocations
var invocations = [ this._formatActionInvocation(action) ];
var invocationLength = invocations[0].length;
var actionLength;
if (action._getSubactions) {
this._indent();
action._getSubactions().forEach(function (subaction) {
var invocationNew = self._formatActionInvocation(subaction);
invocations.push(invocationNew);
invocationLength = Math.max(invocationLength, invocationNew.length);
});
this._dedent();
}
// update the maximum item length
actionLength = invocationLength + this._currentIndent;
this._actionMaxLength = Math.max(this._actionMaxLength, actionLength);
// add the item to the list
this._addItem(this._formatAction, [ action ]);
}
};
/**
* HelpFormatter#addArguments(actions) -> Void
* - actions (array): actions list
*
* Mass add arguments into current section
*
* ##### Example
*
* formatter.startSection(actionGroup.title);
* formatter.addText(actionGroup.description);
* formatter.addArguments(actionGroup._groupActions);
* formatter.endSection();
*
**/
HelpFormatter.prototype.addArguments = function (actions) {
var self = this;
actions.forEach(function (action) {
self.addArgument(action);
});
};
//
// Help-formatting methods
//
/**
* HelpFormatter#formatHelp -> string
*
* Format help
*
* ##### Example
*
* formatter.addText(this.epilog);
* return formatter.formatHelp();
*
**/
HelpFormatter.prototype.formatHelp = function () {
var help = this._rootSection.formatHelp(this);
if (help) {
help = help.replace(this._longBreakMatcher, c.EOL + c.EOL);
help = $$.trimChars(help, c.EOL) + c.EOL;
}
return help;
};
HelpFormatter.prototype._joinParts = function (partStrings) {
return partStrings.filter(function (part) {
return (part && part !== c.SUPPRESS);
}).join('');
};
HelpFormatter.prototype._formatUsage = function (usage, actions, groups, prefix) {
if (!prefix && typeof prefix !== 'string') {
prefix = 'usage: ';
}
actions = actions || [];
groups = groups || [];
// if usage is specified, use that
if (usage) {
usage = sprintf(usage, { prog: this._prog });
// if no optionals or positionals are available, usage is just prog
} else if (!usage && actions.length === 0) {
usage = this._prog;
// if optionals and positionals are available, calculate usage
} else if (!usage) {
var prog = this._prog;
var optionals = [];
var positionals = [];
var actionUsage;
var textWidth;
// split optionals from positionals
actions.forEach(function (action) {
if (action.isOptional()) {
optionals.push(action);
} else {
positionals.push(action);
}
});
// build full usage string
actionUsage = this._formatActionsUsage([].concat(optionals, positionals), groups);
usage = [ prog, actionUsage ].join(' ');
// wrap the usage parts if it's too long
textWidth = this._width - this._currentIndent;
if ((prefix.length + usage.length) > textWidth) {
// break usage into wrappable parts
var regexpPart = new RegExp('\\(.*?\\)+|\\[.*?\\]+|\\S+', 'g');
var optionalUsage = this._formatActionsUsage(optionals, groups);
var positionalUsage = this._formatActionsUsage(positionals, groups);
var optionalParts = optionalUsage.match(regexpPart);
var positionalParts = positionalUsage.match(regexpPart) || [];
if (optionalParts.join(' ') !== optionalUsage) {
throw new Error('assert "optionalParts.join(\' \') === optionalUsage"');
}
if (positionalParts.join(' ') !== positionalUsage) {
throw new Error('assert "positionalParts.join(\' \') === positionalUsage"');
}
// helper for wrapping lines
/*eslint-disable func-style*/ // node 0.10 compat
var _getLines = function (parts, indent, prefix) {
var lines = [];
var line = [];
var lineLength = prefix ? prefix.length - 1 : indent.length - 1;
parts.forEach(function (part) {
if (lineLength + 1 + part.length > textWidth) {
lines.push(indent + line.join(' '));
line = [];
lineLength = indent.length - 1;
}
line.push(part);
lineLength += part.length + 1;
});
if (line) {
lines.push(indent + line.join(' '));
}
if (prefix) {
lines[0] = lines[0].substr(indent.length);
}
return lines;
};
var lines, indent, parts;
// if prog is short, follow it with optionals or positionals
if (prefix.length + prog.length <= 0.75 * textWidth) {
indent = $$.repeat(' ', (prefix.length + prog.length + 1));
if (optionalParts) {
lines = [].concat(
_getLines([ prog ].concat(optionalParts), indent, prefix),
_getLines(positionalParts, indent)
);
} else if (positionalParts) {
lines = _getLines([ prog ].concat(positionalParts), indent, prefix);
} else {
lines = [ prog ];
}
// if prog is long, put it on its own line
} else {
indent = $$.repeat(' ', prefix.length);
parts = optionalParts.concat(positionalParts);
lines = _getLines(parts, indent);
if (lines.length > 1) {
lines = [].concat(
_getLines(optionalParts, indent),
_getLines(positionalParts, indent)
);
}
lines = [ prog ].concat(lines);
}
// join lines into usage
usage = lines.join(c.EOL);
}
}
// prefix with 'usage:'
return prefix + usage + c.EOL + c.EOL;
};
HelpFormatter.prototype._formatActionsUsage = function (actions, groups) {
// find group indices and identify actions in groups
var groupActions = [];
var inserts = [];
var self = this;
groups.forEach(function (group) {
var end;
var i;
var start = actions.indexOf(group._groupActions[0]);
if (start >= 0) {
end = start + group._groupActions.length;
//if (actions.slice(start, end) === group._groupActions) {
if ($$.arrayEqual(actions.slice(start, end), group._groupActions)) {
group._groupActions.forEach(function (action) {
groupActions.push(action);
});
if (!group.required) {
if (inserts[start]) {
inserts[start] += ' [';
} else {
inserts[start] = '[';
}
inserts[end] = ']';
} else {
if (inserts[start]) {
inserts[start] += ' (';
} else {
inserts[start] = '(';
}
inserts[end] = ')';
}
for (i = start + 1; i < end; i += 1) {
inserts[i] = '|';
}
}
}
});
// collect all actions format strings
var parts = [];
actions.forEach(function (action, actionIndex) {
var part;
var optionString;
var argsDefault;
var argsString;
// suppressed arguments are marked with None
// remove | separators for suppressed arguments
if (action.help === c.SUPPRESS) {
parts.push(null);
if (inserts[actionIndex] === '|') {
inserts.splice(actionIndex, actionIndex);
} else if (inserts[actionIndex + 1] === '|') {
inserts.splice(actionIndex + 1, actionIndex + 1);
}
// produce all arg strings
} else if (!action.isOptional()) {
part = self._formatArgs(action, action.dest);
// if it's in a group, strip the outer []
if (groupActions.indexOf(action) >= 0) {
if (part[0] === '[' && part[part.length - 1] === ']') {
part = part.slice(1, -1);
}
}
// add the action string to the list
parts.push(part);
// produce the first way to invoke the option in brackets
} else {
optionString = action.optionStrings[0];
// if the Optional doesn't take a value, format is: -s or --long
if (action.nargs === 0) {
part = '' + optionString;
// if the Optional takes a value, format is: -s ARGS or --long ARGS
} else {
argsDefault = action.dest.toUpperCase();
argsString = self._formatArgs(action, argsDefault);
part = optionString + ' ' + argsString;
}
// make it look optional if it's not required or in a group
if (!action.required && groupActions.indexOf(action) < 0) {
part = '[' + part + ']';
}
// add the action string to the list
parts.push(part);
}
});
// insert things at the necessary indices
for (var i = inserts.length - 1; i >= 0; --i) {
if (inserts[i] !== null) {
parts.splice(i, 0, inserts[i]);
}
}
// join all the action items with spaces
var text = parts.filter(function (part) {
return !!part;
}).join(' ');
// clean up separators for mutually exclusive groups
text = text.replace(/([\[(]) /g, '$1'); // remove spaces
text = text.replace(/ ([\])])/g, '$1');
text = text.replace(/\[ *\]/g, ''); // remove empty groups
text = text.replace(/\( *\)/g, '');
text = text.replace(/\(([^|]*)\)/g, '$1'); // remove () from single action groups
text = text.trim();
// return the text
return text;
};
HelpFormatter.prototype._formatText = function (text) {
text = sprintf(text, { prog: this._prog });
var textWidth = this._width - this._currentIndent;
var indentIncriment = $$.repeat(' ', this._currentIndent);
return this._fillText(text, textWidth, indentIncriment) + c.EOL + c.EOL;
};
HelpFormatter.prototype._formatAction = function (action) {
var self = this;
var helpText;
var helpLines;
var parts;
var indentFirst;
// determine the required width and the entry label
var helpPosition = Math.min(this._actionMaxLength + 2, this._maxHelpPosition);
var helpWidth = this._width - helpPosition;
var actionWidth = helpPosition - this._currentIndent - 2;
var actionHeader = this._formatActionInvocation(action);
// no help; start on same line and add a final newline
if (!action.help) {
actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
// short action name; start on the same line and pad two spaces
} else if (actionHeader.length <= actionWidth) {
actionHeader = $$.repeat(' ', this._currentIndent) +
actionHeader +
' ' +
$$.repeat(' ', actionWidth - actionHeader.length);
indentFirst = 0;
// long action name; start on the next line
} else {
actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
indentFirst = helpPosition;
}
// collect the pieces of the action help
parts = [ actionHeader ];
// if there was help for the action, add lines of help text
if (action.help) {
helpText = this._expandHelp(action);
helpLines = this._splitLines(helpText, helpWidth);
parts.push($$.repeat(' ', indentFirst) + helpLines[0] + c.EOL);
helpLines.slice(1).forEach(function (line) {
parts.push($$.repeat(' ', helpPosition) + line + c.EOL);
});
// or add a newline if the description doesn't end with one
} else if (actionHeader.charAt(actionHeader.length - 1) !== c.EOL) {
parts.push(c.EOL);
}
// if there are any sub-actions, add their help as well
if (action._getSubactions) {
this._indent();
action._getSubactions().forEach(function (subaction) {
parts.push(self._formatAction(subaction));
});
this._dedent();
}
// return a single string
return this._joinParts(parts);
};
HelpFormatter.prototype._formatActionInvocation = function (action) {
if (!action.isOptional()) {
var format_func = this._metavarFormatter(action, action.dest);
var metavars = format_func(1);
return metavars[0];
}
var parts = [];
var argsDefault;
var argsString;
// if the Optional doesn't take a value, format is: -s, --long
if (action.nargs === 0) {
parts = parts.concat(action.optionStrings);
// if the Optional takes a value, format is: -s ARGS, --long ARGS
} else {
argsDefault = action.dest.toUpperCase();
argsString = this._formatArgs(action, argsDefault);
action.optionStrings.forEach(function (optionString) {
parts.push(optionString + ' ' + argsString);
});
}
return parts.join(', ');
};
HelpFormatter.prototype._metavarFormatter = function (action, metavarDefault) {
var result;
if (action.metavar || action.metavar === '') {
result = action.metavar;
} else if (action.choices) {
var choices = action.choices;
if (typeof choices === 'string') {
choices = choices.split('').join(', ');
} else if (Array.isArray(choices)) {
choices = choices.join(',');
} else {
choices = Object.keys(choices).join(',');
}
result = '{' + choices + '}';
} else {
result = metavarDefault;
}
return function (size) {
if (Array.isArray(result)) {
return result;
}
var metavars = [];
for (var i = 0; i < size; i += 1) {
metavars.push(result);
}
return metavars;
};
};
HelpFormatter.prototype._formatArgs = function (action, metavarDefault) {
var result;
var metavars;
var buildMetavar = this._metavarFormatter(action, metavarDefault);
switch (action.nargs) {
/*eslint-disable no-undefined*/
case undefined:
case null:
metavars = buildMetavar(1);
result = '' + metavars[0];
break;
case c.OPTIONAL:
metavars = buildMetavar(1);
result = '[' + metavars[0] + ']';
break;
case c.ZERO_OR_MORE:
metavars = buildMetavar(2);
result = '[' + metavars[0] + ' [' + metavars[1] + ' ...]]';
break;
case c.ONE_OR_MORE:
metavars = buildMetavar(2);
result = '' + metavars[0] + ' [' + metavars[1] + ' ...]';
break;
case c.REMAINDER:
result = '...';
break;
case c.PARSER:
metavars = buildMetavar(1);
result = metavars[0] + ' ...';
break;
default:
metavars = buildMetavar(action.nargs);
result = metavars.join(' ');
}
return result;
};
HelpFormatter.prototype._expandHelp = function (action) {
var params = { prog: this._prog };
Object.keys(action).forEach(function (actionProperty) {
var actionValue = action[actionProperty];
if (actionValue !== c.SUPPRESS) {
params[actionProperty] = actionValue;
}
});
if (params.choices) {
if (typeof params.choices === 'string') {
params.choices = params.choices.split('').join(', ');
} else if (Array.isArray(params.choices)) {
params.choices = params.choices.join(', ');
} else {
params.choices = Object.keys(params.choices).join(', ');
}
}
return sprintf(this._getHelpString(action), params);
};
HelpFormatter.prototype._splitLines = function (text, width) {
var lines = [];
var delimiters = [ ' ', '.', ',', '!', '?' ];
var re = new RegExp('[' + delimiters.join('') + '][^' + delimiters.join('') + ']*$');
text = text.replace(/[\n\|\t]/g, ' ');
text = text.trim();
text = text.replace(this._whitespaceMatcher, ' ');
// Wraps the single paragraph in text (a string) so every line
// is at most width characters long.
text.split(c.EOL).forEach(function (line) {
if (width >= line.length) {
lines.push(line);
return;
}
var wrapStart = 0;
var wrapEnd = width;
var delimiterIndex = 0;
while (wrapEnd <= line.length) {
if (wrapEnd !== line.length && delimiters.indexOf(line[wrapEnd] < -1)) {
delimiterIndex = (re.exec(line.substring(wrapStart, wrapEnd)) || {}).index;
wrapEnd = wrapStart + delimiterIndex + 1;
}
lines.push(line.substring(wrapStart, wrapEnd));
wrapStart = wrapEnd;
wrapEnd += width;
}
if (wrapStart < line.length) {
lines.push(line.substring(wrapStart, wrapEnd));
}
});
return lines;
};
HelpFormatter.prototype._fillText = function (text, width, indent) {
var lines = this._splitLines(text, width);
lines = lines.map(function (line) {
return indent + line;
});
return lines.join(c.EOL);
};
HelpFormatter.prototype._getHelpString = function (action) {
return action.help;
};
+76
View File
@@ -0,0 +1,76 @@
/**
* class Namespace
*
* Simple object for storing attributes. Implements equality by attribute names
* and values, and provides a simple string representation.
*
* See also [original guide][1]
*
* [1]:http://docs.python.org/dev/library/argparse.html#the-namespace-object
**/
'use strict';
var $$ = require('./utils');
/**
* new Namespace(options)
* - options(object): predefined propertis for result object
*
**/
var Namespace = module.exports = function Namespace(options) {
$$.extend(this, options);
};
/**
* Namespace#isset(key) -> Boolean
* - key (string|number): property name
*
* Tells whenever `namespace` contains given `key` or not.
**/
Namespace.prototype.isset = function (key) {
return $$.has(this, key);
};
/**
* Namespace#set(key, value) -> self
* -key (string|number|object): propery name
* -value (mixed): new property value
*
* Set the property named key with value.
* If key object then set all key properties to namespace object
**/
Namespace.prototype.set = function (key, value) {
if (typeof (key) === 'object') {
$$.extend(this, key);
} else {
this[key] = value;
}
return this;
};
/**
* Namespace#get(key, defaultValue) -> mixed
* - key (string|number): property name
* - defaultValue (mixed): default value
*
* Return the property key or defaulValue if not set
**/
Namespace.prototype.get = function (key, defaultValue) {
return !this[key] ? defaultValue : this[key];
};
/**
* Namespace#unset(key, defaultValue) -> mixed
* - key (string|number): property name
* - defaultValue (mixed): default value
*
* Return data[key](and delete it) or defaultValue
**/
Namespace.prototype.unset = function (key, defaultValue) {
var value = this[key];
if (value !== null) {
delete this[key];
return value;
}
return defaultValue;
};
+57
View File
@@ -0,0 +1,57 @@
'use strict';
exports.repeat = function (str, num) {
var result = '';
for (var i = 0; i < num; i++) { result += str; }
return result;
};
exports.arrayEqual = function (a, b) {
if (a.length !== b.length) { return false; }
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) { return false; }
}
return true;
};
exports.trimChars = function (str, chars) {
var start = 0;
var end = str.length - 1;
while (chars.indexOf(str.charAt(start)) >= 0) { start++; }
while (chars.indexOf(str.charAt(end)) >= 0) { end--; }
return str.slice(start, end + 1);
};
exports.capitalize = function (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
exports.arrayUnion = function () {
var result = [];
for (var i = 0, values = {}; i < arguments.length; i++) {
var arr = arguments[i];
for (var j = 0; j < arr.length; j++) {
if (!values[arr[j]]) {
values[arr[j]] = true;
result.push(arr[j]);
}
}
}
return result;
};
function has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
exports.has = has;
exports.extend = function (dest, src) {
for (var i in src) {
if (has(src, i)) { dest[i] = src[i]; }
}
};
exports.trimEnd = function (str) {
return str.replace(/\s+$/g, '');
};
+34
View File
@@ -0,0 +1,34 @@
{
"name": "argparse",
"description": "Very powerful CLI arguments parser. Native port of argparse - python's options parsing library",
"version": "1.0.10",
"keywords": [
"cli",
"parser",
"argparse",
"option",
"args"
],
"contributors": [
"Eugene Shkuropat",
"Paul Jacobson"
],
"files": [
"index.js",
"lib/"
],
"license": "MIT",
"repository": "nodeca/argparse",
"scripts": {
"test": "make test"
},
"dependencies": {
"sprintf-js": "~1.0.2"
},
"devDependencies": {
"eslint": "^2.13.1",
"istanbul": "^0.4.5",
"mocha": "^3.1.0",
"ndoc": "^5.0.1"
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Jameson Little
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+34
View File
@@ -0,0 +1,34 @@
base64-js
=========
`base64-js` does basic base64 encoding/decoding in pure JS.
[![build status](https://secure.travis-ci.org/beatgammit/base64-js.png)](http://travis-ci.org/beatgammit/base64-js)
Many browsers already have base64 encoding/decoding functionality, but it is for text data, not all-purpose binary data.
Sometimes encoding/decoding binary data in the browser is useful, and that is what this module does.
## install
With [npm](https://npmjs.org) do:
`npm install base64-js` and `var base64js = require('base64-js')`
For use in web browsers do:
`<script src="base64js.min.js"></script>`
[Get supported base64-js with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-base64-js?utm_source=npm-base64-js&utm_medium=referral&utm_campaign=readme)
## methods
`base64js` has three exposed functions, `byteLength`, `toByteArray` and `fromByteArray`, which both take a single argument.
* `byteLength` - Takes a base64 string and returns length of byte array
* `toByteArray` - Takes a base64 string and returns a byte array
* `fromByteArray` - Takes a byte array and returns a base64 string
## license
MIT
+1
View File
@@ -0,0 +1 @@
(function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,b.base64js=a()}})(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;c<g.length;c++)a(g[c]);return a}return b}()({"/":[function(a,b,c){'use strict';function d(a){var b=a.length;if(0<b%4)throw new Error("Invalid string. Length must be a multiple of 4");var c=a.indexOf("=");-1===c&&(c=b);var d=c===b?0:4-c%4;return[c,d]}function e(a,b,c){return 3*(b+c)/4-c}function f(a){var b,c,f=d(a),g=f[0],h=f[1],j=new m(e(a,g,h)),k=0,n=0<h?g-4:g;for(c=0;c<n;c+=4)b=l[a.charCodeAt(c)]<<18|l[a.charCodeAt(c+1)]<<12|l[a.charCodeAt(c+2)]<<6|l[a.charCodeAt(c+3)],j[k++]=255&b>>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a.charCodeAt(c)]<<2|l[a.charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a.charCodeAt(c)]<<10|l[a.charCodeAt(c+1)]<<4|l[a.charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}function g(a){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;f<c;f+=3)d=(16711680&a[f]<<16)+(65280&a[f+1]<<8)+(255&a[f+2]),e.push(g(d));return e.join("")}function j(a){for(var b,c=a.length,d=c%3,e=[],f=16383,g=0,j=c-d;g<j;g+=f)e.push(h(a,g,g+f>j?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+"==")):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+"=")),e.join("")}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m="undefined"==typeof Uint8Array?Array:Uint8Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,p=n.length;o<p;++o)k[o]=n[o],l[n.charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])("/")});
+3
View File
@@ -0,0 +1,3 @@
export function byteLength(b64: string): number;
export function toByteArray(b64: string): Uint8Array;
export function fromByteArray(uint8: Uint8Array): string;
+150
View File
@@ -0,0 +1,150 @@
'use strict'
exports.byteLength = byteLength
exports.toByteArray = toByteArray
exports.fromByteArray = fromByteArray
var lookup = []
var revLookup = []
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for (var i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i]
revLookup[code.charCodeAt(i)] = i
}
// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup['-'.charCodeAt(0)] = 62
revLookup['_'.charCodeAt(0)] = 63
function getLens (b64) {
var len = b64.length
if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
}
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf('=')
if (validLen === -1) validLen = len
var placeHoldersLen = validLen === len
? 0
: 4 - (validLen % 4)
return [validLen, placeHoldersLen]
}
// base64 is 4/3 + up to two characters of the original data
function byteLength (b64) {
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function _byteLength (b64, validLen, placeHoldersLen) {
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function toByteArray (b64) {
var tmp
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
var curByte = 0
// if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0
? validLen - 4
: validLen
var i
for (i = 0; i < len; i += 4) {
tmp =
(revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)]
arr[curByte++] = (tmp >> 16) & 0xFF
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 2) {
tmp =
(revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4)
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 1) {
tmp =
(revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2)
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
return arr
}
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] +
lookup[num >> 12 & 0x3F] +
lookup[num >> 6 & 0x3F] +
lookup[num & 0x3F]
}
function encodeChunk (uint8, start, end) {
var tmp
var output = []
for (var i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xFF0000) +
((uint8[i + 1] << 8) & 0xFF00) +
(uint8[i + 2] & 0xFF)
output.push(tripletToBase64(tmp))
}
return output.join('')
}
function fromByteArray (uint8) {
var tmp
var len = uint8.length
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
var parts = []
var maxChunkLength = 16383 // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1]
parts.push(
lookup[tmp >> 2] +
lookup[(tmp << 4) & 0x3F] +
'=='
)
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1]
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3F] +
lookup[(tmp << 2) & 0x3F] +
'='
)
}
return parts.join('')
}
+47
View File
@@ -0,0 +1,47 @@
{
"name": "base64-js",
"description": "Base64 encoding/decoding in pure JS",
"version": "1.5.1",
"author": "T. Jameson Little <t.jameson.little@gmail.com>",
"typings": "index.d.ts",
"bugs": {
"url": "https://github.com/beatgammit/base64-js/issues"
},
"devDependencies": {
"babel-minify": "^0.5.1",
"benchmark": "^2.1.4",
"browserify": "^16.3.0",
"standard": "*",
"tape": "4.x"
},
"homepage": "https://github.com/beatgammit/base64-js",
"keywords": [
"base64"
],
"license": "MIT",
"main": "index.js",
"repository": {
"type": "git",
"url": "git://github.com/beatgammit/base64-js.git"
},
"scripts": {
"build": "browserify -s base64js -r ./ | minify > base64js.min.js",
"lint": "standard",
"test": "npm run lint && npm run unit",
"unit": "tape test/*.js"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
}
+156 -145
View File
@@ -1,46 +1,57 @@
1.20.6 / 2026-07-09 1.20.8
=================== ===================
* Same code base as 1.20.7. This was created to test the new release process.
1.20.7
===================
* deps: qs@~6.16.0
1.20.6
======
* Security fix for [GHSA-v422-hmwv-36x6](https://github.com/expressjs/body-parser/security/advisories/GHSA-v422-hmwv-36x6)
* fix: improve `limit` option validation (#698) * fix: improve `limit` option validation (#698)
* Invalid `limit` values (e.g. unparseable strings or `NaN`) now throw instead of being silently ignored, which previously disabled size limit enforcement * Invalid `limit` values (e.g. unparseable strings or `NaN`) now throw instead of being silently ignored, which previously disabled size limit enforcement
* `null` and `undefined` fall back to the default 100kb limit * `null` and `undefined` fall back to the default 100kb limit
1.20.5 / 2026-04-24 1.20.5
=================== ======
* refactor(json): simplify strict mode error string construction * refactor(json): simplify strict mode error string construction
* fix: extended urlencoded parsing of arrays with >100 elements (#716) * fix: extended urlencoded parsing of arrays with >100 elements (#716)
* deps: qs@~6.15.1 * deps: qs@~6.15.1
1.20.4 / 2025-12-01 1.20.4
=================== ======
* deps: qs@~6.14.0 * deps: qs@~6.14.0
* deps: use tilde notation for dependencies * deps: use tilde notation for dependencies
* deps: http-errors@~2.0.1 * deps: http-errors@~2.0.1
* deps: raw-body@~2.5.3 * deps: raw-body@~2.5.3
1.20.3 / 2024-09-10 1.20.3
=================== ======
* deps: qs@6.13.0 * deps: qs@6.13.0
* add `depth` option to customize the depth level in the parser * add `depth` option to customize the depth level in the parser
* IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`) * IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`)
1.20.2 / 2023-02-21 1.20.2
=================== ======
* Fix strict json error message on Node.js 19+ * Fix strict json error message on Node.js 19+
* deps: content-type@~1.0.5 * deps: content-type@~1.0.5
- perf: skip value escaping when unnecessary - perf: skip value escaping when unnecessary
* deps: raw-body@2.5.2 * deps: raw-body@2.5.2
1.20.1 / 2022-10-06 1.20.1
=================== ======
* deps: qs@6.11.0 * deps: qs@6.11.0
* perf: remove unnecessary object clone * perf: remove unnecessary object clone
1.20.0 / 2022-04-02 1.20.0
=================== ======
* Fix error message for json parse whitespace in `strict` * Fix error message for json parse whitespace in `strict`
* Fix internal error when inflated body exceeds limit * Fix internal error when inflated body exceeds limit
@@ -57,8 +68,8 @@
* deps: raw-body@2.5.1 * deps: raw-body@2.5.1
- deps: http-errors@2.0.0 - deps: http-errors@2.0.0
1.19.2 / 2022-02-15 1.19.2
=================== ======
* deps: bytes@3.1.2 * deps: bytes@3.1.2
* deps: qs@6.9.7 * deps: qs@6.9.7
@@ -66,8 +77,8 @@
* deps: raw-body@2.4.3 * deps: raw-body@2.4.3
- deps: bytes@3.1.2 - deps: bytes@3.1.2
1.19.1 / 2021-12-10 1.19.1
=================== ======
* deps: bytes@3.1.1 * deps: bytes@3.1.1
* deps: http-errors@1.8.1 * deps: http-errors@1.8.1
@@ -81,8 +92,8 @@
* deps: safe-buffer@5.2.1 * deps: safe-buffer@5.2.1
* deps: type-is@~1.6.18 * deps: type-is@~1.6.18
1.19.0 / 2019-04-25 1.19.0
=================== ======
* deps: bytes@3.1.0 * deps: bytes@3.1.0
- Add petabyte (`pb`) support - Add petabyte (`pb`) support
@@ -102,8 +113,8 @@
- deps: mime-types@~2.1.24 - deps: mime-types@~2.1.24
- perf: prevent internal `throw` on invalid type - perf: prevent internal `throw` on invalid type
1.18.3 / 2018-05-14 1.18.3
=================== ======
* Fix stack trace for strict json parse error * Fix stack trace for strict json parse error
* deps: depd@~1.1.2 * deps: depd@~1.1.2
@@ -122,14 +133,14 @@
* deps: type-is@~1.6.16 * deps: type-is@~1.6.16
- deps: mime-types@~2.1.18 - deps: mime-types@~2.1.18
1.18.2 / 2017-09-22 1.18.2
=================== ======
* deps: debug@2.6.9 * deps: debug@2.6.9
* perf: remove argument reassignment * perf: remove argument reassignment
1.18.1 / 2017-09-12 1.18.1
=================== ======
* deps: content-type@~1.0.4 * deps: content-type@~1.0.4
- perf: remove argument reassignment - perf: remove argument reassignment
@@ -142,8 +153,8 @@
* deps: raw-body@2.3.2 * deps: raw-body@2.3.2
- deps: iconv-lite@0.4.19 - deps: iconv-lite@0.4.19
1.18.0 / 2017-09-08 1.18.0
=================== ======
* Fix JSON strict violation error to match native parse error * Fix JSON strict violation error to match native parse error
* Include the `body` property on verify errors * Include the `body` property on verify errors
@@ -168,8 +179,8 @@
- perf: skip buffer decoding on overage chunk - perf: skip buffer decoding on overage chunk
* perf: prevent internal `throw` when missing charset * perf: prevent internal `throw` when missing charset
1.17.2 / 2017-05-17 1.17.2
=================== ======
* deps: debug@2.6.7 * deps: debug@2.6.7
- Fix `DEBUG_MAX_ARRAY_LENGTH` - Fix `DEBUG_MAX_ARRAY_LENGTH`
@@ -177,14 +188,14 @@
* deps: type-is@~1.6.15 * deps: type-is@~1.6.15
- deps: mime-types@~2.1.15 - deps: mime-types@~2.1.15
1.17.1 / 2017-03-06 1.17.1
=================== ======
* deps: qs@6.4.0 * deps: qs@6.4.0
- Fix regression parsing keys starting with `[` - Fix regression parsing keys starting with `[`
1.17.0 / 2017-03-01 1.17.0
=================== ======
* deps: http-errors@~1.6.1 * deps: http-errors@~1.6.1
- Make `message` property enumerable for `HttpError`s - Make `message` property enumerable for `HttpError`s
@@ -192,15 +203,15 @@
* deps: qs@6.3.1 * deps: qs@6.3.1
- Fix compacting nested arrays - Fix compacting nested arrays
1.16.1 / 2017-02-10 1.16.1
=================== ======
* deps: debug@2.6.1 * deps: debug@2.6.1
- Fix deprecation messages in WebStorm and other editors - Fix deprecation messages in WebStorm and other editors
- Undeprecate `DEBUG_FD` set to `1` or `2` - Undeprecate `DEBUG_FD` set to `1` or `2`
1.16.0 / 2017-01-17 1.16.0
=================== ======
* deps: debug@2.6.0 * deps: debug@2.6.0
- Allow colors in workers - Allow colors in workers
@@ -226,8 +237,8 @@
* deps: type-is@~1.6.14 * deps: type-is@~1.6.14
- deps: mime-types@~2.1.13 - deps: mime-types@~2.1.13
1.15.2 / 2016-06-19 1.15.2
=================== ======
* deps: bytes@2.4.0 * deps: bytes@2.4.0
* deps: content-type@~1.0.2 * deps: content-type@~1.0.2
@@ -243,8 +254,8 @@
* deps: type-is@~1.6.13 * deps: type-is@~1.6.13
- deps: mime-types@~2.1.11 - deps: mime-types@~2.1.11
1.15.1 / 2016-05-05 1.15.1
=================== ======
* deps: bytes@2.3.0 * deps: bytes@2.3.0
- Drop partial bytes on all parsed units - Drop partial bytes on all parsed units
@@ -254,8 +265,8 @@
* deps: type-is@~1.6.12 * deps: type-is@~1.6.12
- deps: mime-types@~2.1.10 - deps: mime-types@~2.1.10
1.15.0 / 2016-02-10 1.15.0
=================== ======
* deps: http-errors@~1.4.0 * deps: http-errors@~1.4.0
- Add `HttpError` export, for `err instanceof createError.HttpError` - Add `HttpError` export, for `err instanceof createError.HttpError`
@@ -265,8 +276,8 @@
* deps: type-is@~1.6.11 * deps: type-is@~1.6.11
- deps: mime-types@~2.1.9 - deps: mime-types@~2.1.9
1.14.2 / 2015-12-16 1.14.2
=================== ======
* deps: bytes@2.2.0 * deps: bytes@2.2.0
* deps: iconv-lite@0.4.13 * deps: iconv-lite@0.4.13
@@ -277,8 +288,8 @@
* deps: type-is@~1.6.10 * deps: type-is@~1.6.10
- deps: mime-types@~2.1.8 - deps: mime-types@~2.1.8
1.14.1 / 2015-09-27 1.14.1
=================== ======
* Fix issue where invalid charset results in 400 when `verify` used * Fix issue where invalid charset results in 400 when `verify` used
* deps: iconv-lite@0.4.12 * deps: iconv-lite@0.4.12
@@ -289,8 +300,8 @@
* deps: type-is@~1.6.9 * deps: type-is@~1.6.9
- deps: mime-types@~2.1.7 - deps: mime-types@~2.1.7
1.14.0 / 2015-09-16 1.14.0
=================== ======
* Fix JSON strict parse error to match syntax errors * Fix JSON strict parse error to match syntax errors
* Provide static `require` analysis in `urlencoded` parser * Provide static `require` analysis in `urlencoded` parser
@@ -303,14 +314,14 @@
- Fix type error when given invalid type to match against - Fix type error when given invalid type to match against
- deps: mime-types@~2.1.6 - deps: mime-types@~2.1.6
1.13.3 / 2015-07-31 1.13.3
=================== ======
* deps: type-is@~1.6.6 * deps: type-is@~1.6.6
- deps: mime-types@~2.1.4 - deps: mime-types@~2.1.4
1.13.2 / 2015-07-05 1.13.2
=================== ======
* deps: iconv-lite@0.4.11 * deps: iconv-lite@0.4.11
* deps: qs@4.0.0 * deps: qs@4.0.0
@@ -325,14 +336,14 @@
- perf: enable strict mode - perf: enable strict mode
- perf: remove argument reassignment - perf: remove argument reassignment
1.13.1 / 2015-06-16 1.13.1
=================== ======
* deps: qs@2.4.2 * deps: qs@2.4.2
- Downgraded from 3.1.0 because of user-visible incompatibilities - Downgraded from 3.1.0 because of user-visible incompatibilities
1.13.0 / 2015-06-14 1.13.0
=================== ======
* Add `statusCode` property on `Error`s, in addition to `status` * Add `statusCode` property on `Error`s, in addition to `status`
* Change `type` default to `application/json` for JSON parser * Change `type` default to `application/json` for JSON parser
@@ -363,8 +374,8 @@
* perf: remove argument reassignment * perf: remove argument reassignment
* perf: remove delete call * perf: remove delete call
1.12.4 / 2015-05-10 1.12.4
=================== ======
* deps: debug@~2.2.0 * deps: debug@~2.2.0
* deps: qs@2.4.2 * deps: qs@2.4.2
@@ -376,8 +387,8 @@
* deps: type-is@~1.6.2 * deps: type-is@~1.6.2
- deps: mime-types@~2.0.11 - deps: mime-types@~2.0.11
1.12.3 / 2015-04-15 1.12.3
=================== ======
* Slight efficiency improvement when not debugging * Slight efficiency improvement when not debugging
* deps: depd@~1.0.1 * deps: depd@~1.0.1
@@ -387,14 +398,14 @@
- Fix hanging callback if request aborts during read - Fix hanging callback if request aborts during read
- deps: iconv-lite@0.4.8 - deps: iconv-lite@0.4.8
1.12.2 / 2015-03-16 1.12.2
=================== ======
* deps: qs@2.4.1 * deps: qs@2.4.1
- Fix error when parameter `hasOwnProperty` is present - Fix error when parameter `hasOwnProperty` is present
1.12.1 / 2015-03-15 1.12.1
=================== ======
* deps: debug@~2.1.3 * deps: debug@~2.1.3
- Fix high intensity foreground color for bold - Fix high intensity foreground color for bold
@@ -402,8 +413,8 @@
* deps: type-is@~1.6.1 * deps: type-is@~1.6.1
- deps: mime-types@~2.0.10 - deps: mime-types@~2.0.10
1.12.0 / 2015-02-13 1.12.0
=================== ======
* add `debug` messages * add `debug` messages
* accept a function for the `type` option * accept a function for the `type` option
@@ -418,35 +429,35 @@
- support wildcard for both type and subtype (`*/*`) - support wildcard for both type and subtype (`*/*`)
- deps: mime-types@~2.0.9 - deps: mime-types@~2.0.9
1.11.0 / 2015-01-30 1.11.0
=================== ======
* make internal `extended: true` depth limit infinity * make internal `extended: true` depth limit infinity
* deps: type-is@~1.5.6 * deps: type-is@~1.5.6
- deps: mime-types@~2.0.8 - deps: mime-types@~2.0.8
1.10.2 / 2015-01-20 1.10.2
=================== ======
* deps: iconv-lite@0.4.6 * deps: iconv-lite@0.4.6
- Fix rare aliases of single-byte encodings - Fix rare aliases of single-byte encodings
* deps: raw-body@1.3.2 * deps: raw-body@1.3.2
- deps: iconv-lite@0.4.6 - deps: iconv-lite@0.4.6
1.10.1 / 2015-01-01 1.10.1
=================== ======
* deps: on-finished@~2.2.0 * deps: on-finished@~2.2.0
* deps: type-is@~1.5.5 * deps: type-is@~1.5.5
- deps: mime-types@~2.0.7 - deps: mime-types@~2.0.7
1.10.0 / 2014-12-02 1.10.0
=================== ======
* make internal `extended: true` array limit dynamic * make internal `extended: true` array limit dynamic
1.9.3 / 2014-11-21 1.9.3
================== =====
* deps: iconv-lite@0.4.5 * deps: iconv-lite@0.4.5
- Fix Windows-31J and X-SJIS encoding support - Fix Windows-31J and X-SJIS encoding support
@@ -457,14 +468,14 @@
* deps: type-is@~1.5.3 * deps: type-is@~1.5.3
- deps: mime-types@~2.0.3 - deps: mime-types@~2.0.3
1.9.2 / 2014-10-27 1.9.2
================== =====
* deps: qs@2.3.2 * deps: qs@2.3.2
- Fix parsing of mixed objects and values - Fix parsing of mixed objects and values
1.9.1 / 2014-10-22 1.9.1
================== =====
* deps: on-finished@~2.1.1 * deps: on-finished@~2.1.1
- Fix handling of pipelined requests - Fix handling of pipelined requests
@@ -473,37 +484,37 @@
* deps: type-is@~1.5.2 * deps: type-is@~1.5.2
- deps: mime-types@~2.0.2 - deps: mime-types@~2.0.2
1.9.0 / 2014-09-24 1.9.0
================== =====
* include the charset in "unsupported charset" error message * include the charset in "unsupported charset" error message
* include the encoding in "unsupported content encoding" error message * include the encoding in "unsupported content encoding" error message
* deps: depd@~1.0.0 * deps: depd@~1.0.0
1.8.4 / 2014-09-23 1.8.4
================== =====
* fix content encoding to be case-insensitive * fix content encoding to be case-insensitive
1.8.3 / 2014-09-19 1.8.3
================== =====
* deps: qs@2.2.4 * deps: qs@2.2.4
- Fix issue with object keys starting with numbers truncated - Fix issue with object keys starting with numbers truncated
1.8.2 / 2014-09-15 1.8.2
================== =====
* deps: depd@0.4.5 * deps: depd@0.4.5
1.8.1 / 2014-09-07 1.8.1
================== =====
* deps: media-typer@0.3.0 * deps: media-typer@0.3.0
* deps: type-is@~1.5.1 * deps: type-is@~1.5.1
1.8.0 / 2014-09-05 1.8.0
================== =====
* make empty-body-handling consistent between chunked requests * make empty-body-handling consistent between chunked requests
- empty `json` produces `{}` - empty `json` produces `{}`
@@ -515,56 +526,56 @@
* deps: type-is@~1.5.0 * deps: type-is@~1.5.0
- fix `hasbody` to be true for `content-length: 0` - fix `hasbody` to be true for `content-length: 0`
1.7.0 / 2014-09-01 1.7.0
================== =====
* add `parameterLimit` option to `urlencoded` parser * add `parameterLimit` option to `urlencoded` parser
* change `urlencoded` extended array limit to 100 * change `urlencoded` extended array limit to 100
* respond with 413 when over `parameterLimit` in `urlencoded` * respond with 413 when over `parameterLimit` in `urlencoded`
1.6.7 / 2014-08-29 1.6.7
================== =====
* deps: qs@2.2.2 * deps: qs@2.2.2
- Remove unnecessary cloning - Remove unnecessary cloning
1.6.6 / 2014-08-27 1.6.6
================== =====
* deps: qs@2.2.0 * deps: qs@2.2.0
- Array parsing fix - Array parsing fix
- Performance improvements - Performance improvements
1.6.5 / 2014-08-16 1.6.5
================== =====
* deps: on-finished@2.1.0 * deps: on-finished@2.1.0
1.6.4 / 2014-08-14 1.6.4
================== =====
* deps: qs@1.2.2 * deps: qs@1.2.2
1.6.3 / 2014-08-10 1.6.3
================== =====
* deps: qs@1.2.1 * deps: qs@1.2.1
1.6.2 / 2014-08-07 1.6.2
================== =====
* deps: qs@1.2.0 * deps: qs@1.2.0
- Fix parsing array of objects - Fix parsing array of objects
1.6.1 / 2014-08-06 1.6.1
================== =====
* deps: qs@1.1.0 * deps: qs@1.1.0
- Accept urlencoded square brackets - Accept urlencoded square brackets
- Accept empty values in implicit array notation - Accept empty values in implicit array notation
1.6.0 / 2014-08-05 1.6.0
================== =====
* deps: qs@1.0.2 * deps: qs@1.0.2
- Complete rewrite - Complete rewrite
@@ -572,20 +583,20 @@
- Limits object depth to 5 - Limits object depth to 5
- Limits parameters to 1,000 - Limits parameters to 1,000
1.5.2 / 2014-07-27 1.5.2
================== =====
* deps: depd@0.4.4 * deps: depd@0.4.4
- Work-around v8 generating empty stack traces - Work-around v8 generating empty stack traces
1.5.1 / 2014-07-26 1.5.1
================== =====
* deps: depd@0.4.3 * deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low - Fix exception when global `Error.stackTraceLimit` is too low
1.5.0 / 2014-07-20 1.5.0
================== =====
* deps: depd@0.4.2 * deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable - Add `TRACE_DEPRECATION` environment variable
@@ -600,25 +611,25 @@
- Fix `Cannot switch to old mode now` error on Node.js 0.10+ - Fix `Cannot switch to old mode now` error on Node.js 0.10+
* deps: type-is@~1.3.2 * deps: type-is@~1.3.2
1.4.3 / 2014-06-19 1.4.3
================== =====
* deps: type-is@1.3.1 * deps: type-is@1.3.1
- fix global variable leak - fix global variable leak
1.4.2 / 2014-06-19 1.4.2
================== =====
* deps: type-is@1.3.0 * deps: type-is@1.3.0
- improve type parsing - improve type parsing
1.4.1 / 2014-06-19 1.4.1
================== =====
* fix urlencoded extended deprecation message * fix urlencoded extended deprecation message
1.4.0 / 2014-06-19 1.4.0
================== =====
* add `text` parser * add `text` parser
* add `raw` parser * add `raw` parser
@@ -633,60 +644,60 @@
* deps: raw-body@1.2.2 * deps: raw-body@1.2.2
- Support all encodings from `iconv-lite` - Support all encodings from `iconv-lite`
1.3.1 / 2014-06-11 1.3.1
================== =====
* deps: type-is@1.2.1 * deps: type-is@1.2.1
- Switch dependency from mime to mime-types@1.0.0 - Switch dependency from mime to mime-types@1.0.0
1.3.0 / 2014-05-31 1.3.0
================== =====
* add `extended` option to urlencoded parser * add `extended` option to urlencoded parser
1.2.2 / 2014-05-27 1.2.2
================== =====
* deps: raw-body@1.1.6 * deps: raw-body@1.1.6
- assert stream encoding on node.js 0.8 - assert stream encoding on node.js 0.8
- assert stream encoding on node.js < 0.10.6 - assert stream encoding on node.js < 0.10.6
- deps: bytes@1 - deps: bytes@1
1.2.1 / 2014-05-26 1.2.1
================== =====
* invoke `next(err)` after request fully read * invoke `next(err)` after request fully read
- prevents hung responses and socket hang ups - prevents hung responses and socket hang ups
1.2.0 / 2014-05-11 1.2.0
================== =====
* add `verify` option * add `verify` option
* deps: type-is@1.2.0 * deps: type-is@1.2.0
- support suffix matching - support suffix matching
1.1.2 / 2014-05-11 1.1.2
================== =====
* improve json parser speed * improve json parser speed
1.1.1 / 2014-05-11 1.1.1
================== =====
* fix repeated limit parsing with every request * fix repeated limit parsing with every request
1.1.0 / 2014-05-10 1.1.0
================== =====
* add `type` option * add `type` option
* deps: pin for safety and consistency * deps: pin for safety and consistency
1.0.2 / 2014-04-14 1.0.2
================== =====
* use `type-is` module * use `type-is` module
1.0.1 / 2014-03-20 1.0.1
================== =====
* lower default limits to 100kb * lower default limits to 100kb
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "body-parser", "name": "body-parser",
"description": "Node.js body parsing middleware", "description": "Node.js body parsing middleware",
"version": "1.20.6", "version": "1.20.8",
"contributors": [ "contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>", "Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)" "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
@@ -17,7 +17,7 @@
"http-errors": "~2.0.1", "http-errors": "~2.0.1",
"iconv-lite": "~0.4.24", "iconv-lite": "~0.4.24",
"on-finished": "~2.4.1", "on-finished": "~2.4.1",
"qs": "~6.15.1", "qs": "~6.16.0",
"raw-body": "~2.5.3", "raw-body": "~2.5.3",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
Copyright Node.js contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# core-util-is
The `util.is*` functions introduced in Node v0.12.
+107
View File
@@ -0,0 +1,107 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`.
function isArray(arg) {
if (Array.isArray) {
return Array.isArray(arg);
}
return objectToString(arg) === '[object Array]';
}
exports.isArray = isArray;
function isBoolean(arg) {
return typeof arg === 'boolean';
}
exports.isBoolean = isBoolean;
function isNull(arg) {
return arg === null;
}
exports.isNull = isNull;
function isNullOrUndefined(arg) {
return arg == null;
}
exports.isNullOrUndefined = isNullOrUndefined;
function isNumber(arg) {
return typeof arg === 'number';
}
exports.isNumber = isNumber;
function isString(arg) {
return typeof arg === 'string';
}
exports.isString = isString;
function isSymbol(arg) {
return typeof arg === 'symbol';
}
exports.isSymbol = isSymbol;
function isUndefined(arg) {
return arg === void 0;
}
exports.isUndefined = isUndefined;
function isRegExp(re) {
return objectToString(re) === '[object RegExp]';
}
exports.isRegExp = isRegExp;
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
exports.isObject = isObject;
function isDate(d) {
return objectToString(d) === '[object Date]';
}
exports.isDate = isDate;
function isError(e) {
return (objectToString(e) === '[object Error]' || e instanceof Error);
}
exports.isError = isError;
function isFunction(arg) {
return typeof arg === 'function';
}
exports.isFunction = isFunction;
function isPrimitive(arg) {
return arg === null ||
typeof arg === 'boolean' ||
typeof arg === 'number' ||
typeof arg === 'string' ||
typeof arg === 'symbol' || // ES6 symbol
typeof arg === 'undefined';
}
exports.isPrimitive = isPrimitive;
exports.isBuffer = require('buffer').Buffer.isBuffer;
function objectToString(o) {
return Object.prototype.toString.call(o);
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "core-util-is",
"version": "1.0.3",
"description": "The `util.is*` functions introduced in Node v0.12.",
"main": "lib/util.js",
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git://github.com/isaacs/core-util-is"
},
"keywords": [
"util",
"isBuffer",
"isArray",
"isNumber",
"isString",
"isRegExp",
"isThis",
"isThat",
"polyfill"
],
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/isaacs/core-util-is/issues"
},
"scripts": {
"test": "tap test.js",
"preversion": "npm test",
"postversion": "npm publish",
"prepublishOnly": "git push origin --follow-tags"
},
"devDependencies": {
"tap": "^15.0.9"
}
}
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2021, Michael Williamson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+79
View File
@@ -0,0 +1,79 @@
# dingbat-to-unicode
Mapping from Dingbat fonts, such as Symbol, Webdings and Wingdings, to Unicode code points.
The following fonts are supported:
* Symbol
* Webdings
* Wingdings
* Wingdings 2
* Wingdings 3
Note that in some cases, such as docx files,
the dingbat code point may have 0xF000 added to it to shift the code point into the Unicode private use area.
You should subtract 0xF000 from the code point before passing it into this library.
## Installation
npm install dingbat-to-unicode
## Usage
Import using `require` or `import`:
```javascript
const dingbatToUnicode = require("dingbat-to-unicode");
// or
import * as dingbatToUnicode from "dingbat-to-unicode";
```
You can then call one of the following functions, depending on the representation you have the dingbat code point in:
* `dingbatToUnicode.codePoint(typeface: string, codePoint: number): UnicodeScalarValue | undefined`
* `dingbatToUnicode.dec(typeface: string, dec: string): UnicodeScalarValue | undefined`
* `dingbatToUnicode.hex(typeface: string, hex: string): UnicodeScalarValue | undefined`
`UnicodeScalarValue` is an object with two properties:
* `codePoint`: a `number` representing the Unicode code point
* `string`: a `string` representing the code point as a string
## Examples
```javascript
const result = dingbatToUnicode.codePoint("Wingdings", 41)!!;
assert.strictEqual(result.codePoint, 0x2706);
```
```javascript
const result = dingbatToUnicode.dec("Wingdings", "41")!!;
assert.strictEqual(result.codePoint, 0x2706);
```
```javascript
const result = dingbatToUnicode.hex("Wingdings", "29")!!;
assert.strictEqual(result.codePoint, 0x2706);
```
```javascript
const result = dingbatToUnicode.hex("Wingdings", "3E")!!;
assert.strictEqual(result.codePoint, 0x2707);
```
```javascript
const result = dingbatToUnicode.hex("Wingdings", "3e")!!;
assert.strictEqual(result.codePoint, 0x2707);
```
```javascript
const result = dingbatToUnicode.hex("Wingdings", "29")!!;
assert.strictEqual(result.string, "\u2706");
```
```javascript
const result = dingbatToUnicode.hex("Wingdings", "28")!!;
assert.strictEqual(result.string, "🕿");
```
+6
View File
@@ -0,0 +1,6 @@
declare const dingbats: {
[typefaceName: string]: {
[dingbatCodePoint: string]: number;
};
};
export default dingbats;
+1076
View File
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
export interface UnicodeScalarValue {
codePoint: number;
string: string;
}
export declare function codePoint(typeface: string, codePoint: number): UnicodeScalarValue | undefined;
export declare function dec(typeface: string, dec: string): UnicodeScalarValue | undefined;
export declare function hex(typeface: string, hex: string): UnicodeScalarValue | undefined;
+46
View File
@@ -0,0 +1,46 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hex = exports.dec = exports.codePoint = void 0;
var dingbats_1 = __importDefault(require("./dingbats"));
var dingbatsByCodePoint = {};
var fromCodePoint = String.fromCodePoint ? String.fromCodePoint : fromCodePointPolyfill;
function codePoint(typeface, codePoint) {
var codePointMap = dingbats_1.default[typeface.toUpperCase()];
if (codePointMap === undefined) {
return undefined;
}
var unicodeCodePoint = codePointMap[codePoint];
if (unicodeCodePoint === undefined) {
return undefined;
}
return {
codePoint: unicodeCodePoint,
string: fromCodePoint(unicodeCodePoint),
};
}
exports.codePoint = codePoint;
function dec(typeface, dec) {
return codePoint(typeface, parseInt(dec, 10));
}
exports.dec = dec;
function hex(typeface, hex) {
return codePoint(typeface, parseInt(hex, 16));
}
exports.hex = hex;
function fromCodePointPolyfill(codePoint) {
if (codePoint <= 0xFFFF) {
// BMP
return String.fromCharCode(codePoint);
}
else {
// Astral
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
var highSurrogate = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800;
var lowSurrogate = (codePoint - 0x10000) % 0x400 + 0xDC00;
return String.fromCharCode(highSurrogate, lowSurrogate);
}
}
;
+39
View File
@@ -0,0 +1,39 @@
{
"name": "dingbat-to-unicode",
"version": "1.0.2",
"description": "Mapping from Dingbat fonts, such as Symbol, Webdings and Wingdings, to Unicode code points",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prepare": "tsc",
"test": "mocha --ui exports --require ts-node/register 'test/**/*.ts'"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mwilliamson/dingbat-to-unicode.git"
},
"keywords": [
"dingbat",
"dingbats",
"symbol",
"webdings",
"wingdings",
"unicode"
],
"author": "Michael Williamson <mike@zwobble.org>",
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/mwilliamson/dingbat-to-unicode/issues"
},
"homepage": "https://github.com/mwilliamson/dingbat-to-unicode#readme",
"devDependencies": {
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.21",
"mocha": "^6.2.3",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}
Generated Vendored
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2013, Michael Williamson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+184
View File
@@ -0,0 +1,184 @@
# duck.js -- rich matchers with helpful messages on match failure
duck.js allows you to perform assertions on complex objects.
When those assertions fail, duck.js will try to produce helpful error messages.
For instance, suppose you want to assert the same property on an array of objects:
```javascript
var duck = require("duck");
var isArray = duck.isArray;
var hasProperties = duck.hasProperties;
var users = fetchUsers();
duck.assertThat(users, isArray([
hasProperties({name: "Bob"}),
hasProperties({name: "Jim"}),
]));
```
which might produce an error message like:
```
Expected [object with properties {
name: 'Bob'
}, object with properties {
name: 'Jim'
}]
but element at index 0 didn't match:
value of property "name" didn't match:
was 'Jim'
expected 'Bob'
expected object with properties {
name: 'Bob'
}
element at index 1 didn't match:
value of property "name" didn't match:
was 'Bob'
expected 'Jim'
expected object with properties {
name: 'Jim'
}
```
## API
The below is a quick reference to the API.
For more examples, take a look at the tests.
### duck.assertThat(value, matcher)
Assert that `value` satifies `matcher`.
If `value` satifies `matcher`, return normally, otherwise throw an
AssertionError describing the mismatch.
### duck.is(value)
If `value` is a matcher, return that matcher,
otherwise return `duck.equalTo(value)`.
### duck.equalTo(value)
Matcher for deep equality on `value`.
### duck.isObject(matcherObj)
An object `obj` matches `duck.isObject(matcherObj)` if:
* `obj` matches `duck.hasProperties(matcherObj)`, and
* there is no key that is present in `obj` but not in `matcherObj`
Sample usage:
```javascript
duck.isObject({
name: "Bob",
address: duck.isObject({
city: "Cambridge",
county: "UK"
})
})
```
`duck.is` is called on each value of the matcher object, meaning that the
above is equivalent to:
```javascript
duck.isObject({
name: duck.is("Bob"),
address: duck.isObject({
city: duck.is("Cambridge"),
county: duck.is("UK")
})
})
```
### duck.hasProperties(matcherProperties)
An object `obj` matches `duck.hasProperties(matcherProperties)` if,
for each `key` in `matcherProperties`, `matcherProperties[key].matches(obj[key])`
Sample usage:
```javascript
duck.hasProperties({
name: "Bob",
address: duck.hasProperties({
city: "Cambridge",
county: "UK"
})
})
```
`duck.is` is called on each value of the matcher object, meaning that the
above is equivalent to:
```javascript
duck.hasProperties({
name: duck.is("Bob"),
address: duck.hasProperties({
city: duck.is("Cambridge"),
county: duck.is("UK")
})
})
```
### duck.isArray(matcherArray)
An array `blah` matches `duck.isArray(matcherArray)` if:
* `blah.length == matcherArray.length`, and
* For `0 <= i < array.length`, `matcherArray[i].matches(blah[i])`
Sample usage:
```javascript
duck.isArray([
duck.hasProperties({name: "Bob"}),
duck.hasProperties({name: "Jim"}),
]))
```
`duck.is` is called on each element of the matcher array, meaning that the
following are equivalent:
```javascript
duck.isArray(["apple", "banana"])
duck.isArray([duck.is("apple"), duck.is("banana")])
```
### Matcher
Each matcher has the following methods:
#### matcher.matches(value)
Return `true` if `value` satifies this matcher, false otherwise.
#### matcher.describeMismatch(value)
Generate a string describing why `value` doesn't satisfy this matcher.
Behaviour is undefined if `value` actually satisifies the matcher.
#### matcher.matchesWithDescription(value)
Equivalent to:
```javascript
var isMatch = this.matches(value);
return {
matches: isMatch,
description: isMatch ? "" : this.describeMismatch(value)
};
```
Useful if you're likely to want both the boolean and the mismatch description.
#### matcher.describeSelf()
Generate a string describing the matcher.
## Thanks
Thanks to [Hamcrest](http://hamcrest.org/) for inspiration.
Generated Vendored Executable
+267
View File
@@ -0,0 +1,267 @@
var util = require("util");
var assert = require("assert");
var _ = require("underscore");
var inspect = function(value) {
return util.inspect(value, false, null);
};
exports.assertThat = function(value, matcher) {
var result = matcher.matchesWithDescription(value);
var message = "Expected " + matcher.describeSelf() +
"\nbut " + result.description;
assert.ok(result.matches, message);
};
exports.is = function(value) {
if (value && value._isDuckMatcher) {
return value;
} else {
return equalTo(value);
}
};
var equalTo = exports.equalTo = function(matchValue) {
return new Matcher({
matches: function(value) {
return _.isEqual(value, matchValue);
},
describeMismatch: function(value) {
return "was " + inspect(value);
},
describeSelf: function() {
return inspect(matchValue);
}
});
};
exports.isObject = function(object) {
var matchers = valuesToMatchers(object);
return new Matcher({
matchesWithDescription: function(value) {
var expectedKeys = ownKeys(object);
var hasPropertiesResult = exports.hasProperties(matchers).matchesWithDescription(value);
var unexpectedPropertyMismatches = ownKeys(value).filter(function(key) {
return expectedKeys.indexOf(key) === -1
}).map(function(key) {
return "unexpected property: \"" + key + "\"";
});
var mismatchDescriptions =
(hasPropertiesResult.matches ? [] : [hasPropertiesResult.description])
.concat(unexpectedPropertyMismatches);
if (mismatchDescriptions.length === 0) {
return {matches: true};
} else {
return {matches: false, description: mismatchDescriptions.join("\n")};
}
},
describeSelf: function() {
return formatObjectOfMatchers(matchers);
}
});
};
exports.hasProperties = function(object) {
var matchers = valuesToMatchers(object);
return new Matcher({
matchesWithDescription: function(value) {
var expectedKeys = ownKeys(object);
expectedKeys.sort(function(first, second) {
if (first < second) {
return -1;
} else if (first > second) {
return 1;
} else {
return 0;
}
});
var propertyResults = expectedKeys.map(function(key) {
var propertyMatcher = matchers[key];
if (!objectHasOwnProperty(value, key)) {
return {matches: false, description: util.format("missing property: \"%s\"", key)};
} else if (!propertyMatcher.matches(value[key])) {
var description = "value of property \"" + key + "\" didn't match:\n" +
" " + indent(propertyMatcher.describeMismatch(value[key]), 1) + "\n" +
" expected " + indent(propertyMatcher.describeSelf(), 1);
return {matches: false, description: description};
} else {
return {matches: true};
}
});
return combineMatchResults(propertyResults);
},
describeSelf: function() {
return "object with properties " + formatObjectOfMatchers(matchers);
}
});
};
exports.isArray = function(expectedArray) {
var elementMatchers = expectedArray.map(exports.is);
return new Matcher({
matchesWithDescription: function(value) {
if (value.length !== elementMatchers.length) {
return {matches: false, description: "was of length " + value.length};
} else {
var elementResults = _.zip(elementMatchers, value).map(function(values, index) {
var expectedMatcher = values[0];
var actual = values[1];
if (expectedMatcher.matches(actual)) {
return {matches: true};
} else {
var description = "element at index " + index + " didn't match:\n " + indent(expectedMatcher.describeMismatch(actual), 1)
+ "\n expected " + indent(expectedMatcher.describeSelf(), 1);
return {matches: false, description: description};
}
});
return combineMatchResults(elementResults);
}
},
describeSelf: function() {
return util.format("[%s]", _.invoke(elementMatchers, "describeSelf").join(", "));
}
});
};
var Matcher = function(matcher) {
this._matcher = matcher;
this._isDuckMatcher = true;
};
Matcher.prototype.matches = function(value) {
if (this._matcher.matches) {
return this._matcher.matches(value);
} else {
return this._matcher.matchesWithDescription(value).matches;
}
};
Matcher.prototype.describeMismatch = function(value) {
if (this._matcher.describeMismatch) {
return this._matcher.describeMismatch(value);
} else {
return this._matcher.matchesWithDescription(value).description;
}
};
Matcher.prototype.matchesWithDescription = function(value) {
if (this._matcher.matchesWithDescription) {
var result = this._matcher.matchesWithDescription(value);
if (result.matches) {
return {
matches: true,
description: ""
};
} else {
return result;
}
} else {
var isMatch = this.matches(value);
return {
matches: isMatch,
description: isMatch ? "" : this.describeMismatch(value)
};
}
};
Matcher.prototype.describeSelf = function() {
return this._matcher.describeSelf();
};
var combineMatchResults = function(results) {
var mismatches = results.filter(function(result) {
return !result.matches;
});
return combineMismatchs(mismatches);
};
var combineMismatchs = function(mismatches) {
if (mismatches.length === 0) {
return {matches: true};
} else {
var mismatchDescriptions = mismatches.map(function(mismatch) {
return mismatch.description;
});
return {matches: false, description: mismatchDescriptions.join("\n")};
}
};
var ownKeys = function(obj) {
var keys = [];
for (var key in obj) {
if (objectHasOwnProperty(obj, key)) {
keys.push(key);
}
}
return keys;
};
var objectHasOwnProperty = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
var objectMap = function(obj, func) {
var matchers = {};
_.forEach(obj, function(value, key) {
if (_.has(obj, key)) {
matchers[key] = func(value, key);
}
});
return matchers;
};
var valuesToMatchers = function(obj) {
return objectMap(obj, exports.is);
};
var formatObject = function(obj) {
if (_.size(obj) === 0) {
return "{}";
} else {
return util.format("{%s\n}", formatProperties(obj));
}
};
var formatProperties = function(obj) {
var properties = _.map(obj, function(value, key) {
return {key: key, value: value};
});
var sortedProperties = _.sortBy(properties, function(property) {
return property.key;
});
return "\n " + sortedProperties.map(function(property) {
return indent(property.key + ": " + property.value, 1);
}).join(",\n ");
};
var formatObjectOfMatchers = function(matchers) {
return formatObject(objectMap(matchers, function(matcher) {
return matcher.describeSelf();
}));
};
var indent = function(str, indentationLevel) {
var indentation = _.range(indentationLevel).map(function() {
return " ";
}).join("");
return str.replace(/\n/g, "\n" + indentation);
};
exports.any = new Matcher({
matchesWithDescription: function() {
return {matches: true};
},
describeSelf: function() {
return "<any>";
}
});
Generated Vendored Executable
+4
View File
@@ -0,0 +1,4 @@
.PHONY: test
test:
node_modules/.bin/nodeunit `find test -name \*.test.js -type f`
+23
View File
@@ -0,0 +1,23 @@
{
"name": "duck",
"version": "0.1.12",
"author": "Michael Williamson <mike@zwobble.org>",
"description": "Rich matchers inspired by Hamcrest. Useful for generating helpful assertion failure messages in tests.",
"license": "BSD",
"repository": {
"type": "git",
"url": "https://github.com/mwilliamson/duck.js.git"
},
"keywords": [
"test",
"assert",
"matcher",
"assertThat"
],
"dependencies": {
"underscore": "^1.13.1"
},
"devDependencies": {
"nodeunit": "~0.8.0"
}
}
Generated Vendored Executable
+160
View File
@@ -0,0 +1,160 @@
var duck = require("../");
var testMatcher = function(options) {
return function(test) {
var matcher = options.matcher;
var description = options.description;
var positives = options.positives;
var negatives = options.negatives;
test.equal(matcher.describeSelf(), description);
positives.forEach(function(positive) {
test.same(true, matcher.matches(positive));
var result = matcher.matchesWithDescription(positive);
test.same(true, result.matches);
test.same("", result.description);
});
negatives.forEach(function(negative) {
test.same(false, matcher.matches(negative.value));
test.same(negative.description, matcher.describeMismatch(negative.value));
var result = matcher.matchesWithDescription(negative.value);
test.same(false, result.matches);
test.same(negative.description, result.description);
});
test.done();
};
};
exports.isMatchesPrimitiveValues = testMatcher({
matcher: duck.is(1),
description: "1",
positives: [1],
negatives: [
{value: 2, description: "was 2"},
{value: "1", description: "was '1'"},
{value: null, description: "was null"}
]
});
exports.isMatchesObjectUsingIsEqual = testMatcher({
matcher: duck.is({}),
description: "{}",
positives: [{}],
negatives: [
{value: {hair: "none"}, description: "was { hair: 'none' }"}
]
});
exports.isDoesNothingToMatchers = testMatcher({
matcher: duck.is(duck.is(1)),
description: "1",
positives: [1],
negatives: [
{value: 2, description: "was 2"},
{value: "1", description: "was '1'"},
{value: null, description: "was null"}
]
});
exports.isObjectMatchesValuesExactly = testMatcher({
matcher: duck.isObject({
name: "Bob",
age: 24
}),
description: "{\n age: 24,\n name: 'Bob'\n}",
positives: [{name: "Bob", age: 24}],
negatives: [
{value: {name: "Bob"}, description: "missing property: \"age\""},
{value: {}, description: "missing property: \"age\"\nmissing property: \"name\""},
{value: {name: "bob", age: 24}, description: "value of property \"name\" didn't match:\n was 'bob'\n expected 'Bob'"},
{value: {name: "Bob", age: 24, hair: "none"}, description: "unexpected property: \"hair\""}
]
});
exports.isObjectSubDescriptionsAreIndented = testMatcher({
matcher: duck.isObject({artist: duck.isObject({name: "Bob"})}),
description: "{\n artist: {\n name: 'Bob'\n }\n}",
positives: [],
negatives: [
{
value: {artist: {name: "Jim"}},
description:
"value of property \"artist\" didn't match:\n" +
" value of property \"name\" didn't match:\n" +
" was 'Jim'\n" +
" expected 'Bob'\n" +
" expected {\n" +
" name: 'Bob'\n" +
" }"
}
]
});
exports.hasPropertiesBehavesAsIsObjectExceptIgnoresUnexpectedValues = testMatcher({
matcher: duck.hasProperties({
name: "Bob",
age: 24
}),
description: "object with properties {\n age: 24,\n name: 'Bob'\n}",
positives: [{name: "Bob", age: 24}, {name: "Bob", age: 24, hair: "none"}],
negatives: [
{value: {name: "Bob"}, description: "missing property: \"age\""},
{value: {}, description: "missing property: \"age\"\nmissing property: \"name\""},
{value: {name: "bob", age: 24}, description: "value of property \"name\" didn't match:\n was 'bob'\n expected 'Bob'"}
]
});
exports.isArrayMatchesLengthAndIndividualElements = testMatcher({
matcher: duck.isArray(["apple", "banana"]),
description: "['apple', 'banana']",
positives: [["apple", "banana"]],
negatives: [
{value: [], description: "was of length 0"},
{value: ["apple", "banana", "coconut"], description: "was of length 3"},
{value: ["apple", "coconut",], description: "element at index 1 didn't match:\n was 'coconut'\n expected 'banana'"}
]
});
exports.isArraySubDescriptionsAreIndented = testMatcher({
matcher: duck.isArray([duck.isObject({name: "Bob"})]),
description: "[{\n name: 'Bob'\n}]",
positives: [[{name: "Bob"}]],
negatives: [
{
value: [{name: "Jim"}],
description:
"element at index 0 didn't match:\n" +
" value of property \"name\" didn't match:\n" +
" was 'Jim'\n" +
" expected 'Bob'\n" +
" expected {\n" +
" name: 'Bob'\n" +
" }"
}
]
});
exports.isArraySubDescriptionsAreIndented = testMatcher({
matcher: duck.any,
description: "<any>",
positives: [{name: "Bob"}, null, undefined, 0, 1, "Bob", []],
negatives: []
});
exports.assertReturnsNormallyOnSuccess = function(test) {
duck.assertThat(42, duck.is(42));
test.done();
};
exports.assertRaisesErrorIfAssertionFails = function(test) {
try {
duck.assertThat(42, duck.is(43));
test.fail("Should throw error");
} catch (error) {
test.equal(error.name, "AssertionError");
test.equal(error.message, "Expected 43\nbut was 42");
}
test.done();
};
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+583 -576
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -469,8 +469,8 @@ defineGetter(req, 'fresh', function(){
var res = this.res var res = this.res
var status = res.statusCode var status = res.statusCode
// GET or HEAD for weak freshness validation only // GET, HEAD, or QUERY for weak freshness validation only
if ('GET' !== method && 'HEAD' !== method) return false; if ('GET' !== method && 'HEAD' !== method && 'QUERY' !== method) return false;
// 2xx or 304 as per rfc2616 14.26 // 2xx or 304 as per rfc2616 14.26
if ((status >= 200 && status < 300) || 304 === status) { if ((status >= 200 && status < 300) || 304 === status) {
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "express", "name": "express",
"description": "Fast, unopinionated, minimalist web framework", "description": "Fast, unopinionated, minimalist web framework",
"version": "4.22.2", "version": "4.22.3",
"author": "TJ Holowaychuk <tj@vision-media.ca>", "author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [ "contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>", "Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -51,9 +51,9 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "~2.4.1", "on-finished": "~2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12", "path-to-regexp": "~0.1.13",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "~6.15.1", "qs": "~6.16.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "~0.19.0", "send": "~0.19.0",
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, Domenic Denicola, Brian Cavalier
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
# immediate [![Build Status](https://travis-ci.org/calvinmetcalf/immediate.svg?branch=master)](https://travis-ci.org/calvinmetcalf/immediate)
```
npm install immediate --save
```
then
```js
var immediate = require("immediate");
immediate(function () {
// this will run soon
});
immediate(function (arg1, arg2) {
// get your args like in iojs
}, thing1, thing2);
```
## Introduction
**immediate** is a microtask library, decended from [NobleJS's setImmediate](https://github.com/NobleJS/setImmediate), but including ideas from [Cujo's When](https://github.com/cujojs/when) and [RSVP][RSVP].
immediate takes the tricks from setImmedate and RSVP and combines them with the schedualer inspired (vaugly) by whens.
Note versions 2.6.5 and earlier were strictly speaking a 'macrotask' library not a microtask one, [see this for the difference](https://github.com/YuzuJS/setImmediate#macrotasks-and-microtasks), if you need a macrotask library, [I got you covered](https://github.com/calvinmetcalf/macrotask).
Several new features were added in versions 3.1.0 and 3.2.0 to maintain parity with
process.nextTick, but the 3.0.x series is still being kept up to date if you just need
the small barebones version.
## The Tricks
### `process.nextTick`
Note that we check for *actual* Node.js environments, not emulated ones like those produced by browserify or similar.
### `MutationObserver`
This is what [RSVP][RSVP] uses, it's very fast, details on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
### `MessageChannel`
Unfortunately, `postMessage` has completely different semantics inside web workers, and so cannot be used there. So we
turn to [`MessageChannel`][MessageChannel], which has worse browser support, but does work inside a web worker.
### `<script> onreadystatechange`
For our last trick, we pull something out to make things fast in Internet Explorer versions 6 through 8: namely,
creating a `<script>` element and firing our calls in its `onreadystatechange` event. This does execute in a future
turn of the event loop, and is also faster than `setTimeout(…, 0)`, so hey, why not?
## Tricks we don't use
### `setImmediate`
We avoid this process.nextTick in node is better suited to our needs and in Internet Explorer 10 there is a broken version of setImmediate we avoid using this.
In Node.js, do
```
npm install immediate
```
then
```js
var immediate = require("immediate");
```
## Reference and Reading
* [Efficient Script Yielding W3C Editor's Draft][spec]
* [W3C mailing list post introducing the specification][list-post]
* [IE Test Drive demo][ie-demo]
* [Introductory blog post by Nicholas C. Zakas][ncz]
* I wrote a couple blog pots on this, [part 1][my-blog-1] and [part 2][my-blog-2]
[RSVP]: https://github.com/tildeio/rsvp.js
[spec]: https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/setImmediate/Overview.html
[list-post]: http://lists.w3.org/Archives/Public/public-web-perf/2011Jun/0100.html
[ie-demo]: http://ie.microsoft.com/testdrive/Performance/setImmediateSorting/Default.html
[ncz]: http://www.nczonline.net/blog/2011/09/19/script-yielding-with-setimmediate/
[nextTick]: http://nodejs.org/docs/v0.8.16/api/process.html#process_process_nexttick_callback
[postMessage]: http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#posting-messages
[MessageChannel]: http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#channel-messaging
[cross-browser-demo]: http://calvinmetcalf.github.io/setImmediate-shim-demo
[my-blog-1]:http://calvinmetcalf.com/post/61672207151/setimmediate-etc
[my-blog-2]:http://calvinmetcalf.com/post/61761231881/javascript-schedulers
+75
View File
@@ -0,0 +1,75 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.immediate = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
(function (global){
'use strict';
var Mutation = global.MutationObserver || global.WebKitMutationObserver;
var scheduleDrain;
{
if (Mutation) {
var called = 0;
var observer = new Mutation(nextTick);
var element = global.document.createTextNode('');
observer.observe(element, {
characterData: true
});
scheduleDrain = function () {
element.data = (called = ++called % 2);
};
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
var channel = new global.MessageChannel();
channel.port1.onmessage = nextTick;
scheduleDrain = function () {
channel.port2.postMessage(0);
};
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
scheduleDrain = function () {
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement('script');
scriptEl.onreadystatechange = function () {
nextTick();
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
};
} else {
scheduleDrain = function () {
setTimeout(nextTick, 0);
};
}
}
var draining;
var queue = [];
//named nextTick for less confusing stack traces
function nextTick() {
draining = true;
var i, oldQueue;
var len = queue.length;
while (len) {
oldQueue = queue;
queue = [];
i = -1;
while (++i < len) {
oldQueue[i]();
}
len = queue.length;
}
draining = false;
}
module.exports = immediate;
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[1])(1)
});
+1
View File
@@ -0,0 +1 @@
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,n.immediate=e()}}(function(){return function e(n,t,o){function r(f,u){if(!t[f]){if(!n[f]){var a="function"==typeof require&&require;if(!u&&a)return a(f,!0);if(i)return i(f,!0);var d=new Error("Cannot find module '"+f+"'");throw d.code="MODULE_NOT_FOUND",d}var s=t[f]={exports:{}};n[f][0].call(s.exports,function(e){var t=n[f][1][e];return r(t?t:e)},s,s.exports,e,n,t,o)}return t[f].exports}for(var i="function"==typeof require&&require,f=0;f<o.length;f++)r(o[f]);return r}({1:[function(e,n,t){(function(e){"use strict";function t(){s=!0;for(var e,n,t=c.length;t;){for(n=c,c=[],e=-1;++e<t;)n[e]();t=c.length}s=!1}function o(e){1!==c.push(e)||s||r()}var r,i=e.MutationObserver||e.WebKitMutationObserver;if(i){var f=0,u=new i(t),a=e.document.createTextNode("");u.observe(a,{characterData:!0}),r=function(){a.data=f=++f%2}}else if(e.setImmediate||"undefined"==typeof e.MessageChannel)r="document"in e&&"onreadystatechange"in e.document.createElement("script")?function(){var n=e.document.createElement("script");n.onreadystatechange=function(){t(),n.onreadystatechange=null,n.parentNode.removeChild(n),n=null},e.document.documentElement.appendChild(n)}:function(){setTimeout(t,0)};else{var d=new e.MessageChannel;d.port1.onmessage=t,r=function(){d.port2.postMessage(0)}}var s,c=[];n.exports=o}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)});
+69
View File
@@ -0,0 +1,69 @@
'use strict';
var Mutation = global.MutationObserver || global.WebKitMutationObserver;
var scheduleDrain;
{
if (Mutation) {
var called = 0;
var observer = new Mutation(nextTick);
var element = global.document.createTextNode('');
observer.observe(element, {
characterData: true
});
scheduleDrain = function () {
element.data = (called = ++called % 2);
};
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
var channel = new global.MessageChannel();
channel.port1.onmessage = nextTick;
scheduleDrain = function () {
channel.port2.postMessage(0);
};
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
scheduleDrain = function () {
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement('script');
scriptEl.onreadystatechange = function () {
nextTick();
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
};
} else {
scheduleDrain = function () {
setTimeout(nextTick, 0);
};
}
}
var draining;
var queue = [];
//named nextTick for less confusing stack traces
function nextTick() {
draining = true;
var i, oldQueue;
var len = queue.length;
while (len) {
oldQueue = queue;
queue = [];
i = -1;
while (++i < len) {
oldQueue[i]();
}
len = queue.length;
}
draining = false;
}
module.exports = immediate;
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}
+73
View File
@@ -0,0 +1,73 @@
'use strict';
var Mutation = global.MutationObserver || global.WebKitMutationObserver;
var scheduleDrain;
if (process.browser) {
if (Mutation) {
var called = 0;
var observer = new Mutation(nextTick);
var element = global.document.createTextNode('');
observer.observe(element, {
characterData: true
});
scheduleDrain = function () {
element.data = (called = ++called % 2);
};
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
var channel = new global.MessageChannel();
channel.port1.onmessage = nextTick;
scheduleDrain = function () {
channel.port2.postMessage(0);
};
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
scheduleDrain = function () {
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement('script');
scriptEl.onreadystatechange = function () {
nextTick();
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
};
} else {
scheduleDrain = function () {
setTimeout(nextTick, 0);
};
}
} else {
scheduleDrain = function () {
process.nextTick(nextTick);
};
}
var draining;
var queue = [];
//named nextTick for less confusing stack traces
function nextTick() {
draining = true;
var i, oldQueue;
var len = queue.length;
while (len) {
oldQueue = queue;
queue = [];
i = -1;
while (++i < len) {
oldQueue[i]();
}
len = queue.length;
}
draining = false;
}
module.exports = immediate;
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "immediate",
"version": "3.0.6",
"description": "A cross browser microtask library",
"contributors": [
"Domenic Denicola <domenic@domenicdenicola.com> (http://domenicdenicola.com)",
"Donavon West <github@donavon.com> (http://donavon.com)",
"Yaffle",
"Calvin Metcalf <calvin.metcalf@gmail.com>"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/calvinmetcalf/immediate.git"
},
"files": [
"lib",
"dist"
],
"bugs": "https://github.com/calvinmetcalf/immediate/issues",
"main": "lib/index.js",
"scripts": {
"build": "npm run build-node && npm run build-js && npm run uglify",
"build-node": "browserify-transform-cli inline-process-browser unreachable-branch-transform < ./lib/index.js > ./lib/browser.js",
"uglify": "uglifyjs dist/immediate.js -mc > dist/immediate.min.js",
"build-js": "browserify -s immediate ./lib/browser.js | derequire > dist/immediate.js",
"test": "jshint lib/*.js && node test/tests.js"
},
"browser": {
"./lib/index.js": "./lib/browser.js"
},
"devDependencies": {
"browserify": "^13.0.0",
"browserify-transform-cli": "^1.1.1",
"derequire": "^2.0.0",
"inline-process-browser": "^2.0.0",
"jshint": "^2.5.1",
"tape": "^4.0.0",
"uglify-js": "^2.4.13",
"unreachable-branch-transform": "^0.5.1"
}
}
+1
View File
@@ -0,0 +1 @@
node_modules
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "0.8"
- "0.10"
Loaded 100 of 1865 files, more files were not shown because too many files have changed in this diff. Show more