@heripo/document-processor 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +71 -9
- package/README.md +87 -9
- package/dist/index.cjs +295 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -7
- package/dist/index.d.ts +84 -7
- package/dist/index.js +294 -34
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [사용법](#사용법)
|
|
21
21
|
- [처리 파이프라인](#처리-파이프라인)
|
|
22
22
|
- [API 문서](#api-문서)
|
|
23
|
+
- [후원](#후원)
|
|
23
24
|
- [라이선스](#라이선스)
|
|
24
25
|
|
|
25
26
|
## 주요 기능
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
- **계층 구조**: 장/절/소절 계층 구조 자동 생성
|
|
29
30
|
- **페이지 매핑**: Vision LLM을 활용한 실제 페이지 번호 매핑
|
|
30
31
|
- **캡션 파싱**: 이미지 및 테이블 캡션 자동 파싱
|
|
32
|
+
- **원천 추적성**: Docling 원천 metadata와 node-level reference 보존
|
|
33
|
+
- **테이블 그리드 정규화**: row/column span을 보존하고 병합 셀 shadow entry 제거
|
|
31
34
|
- **LLM 유연성**: OpenAI, Anthropic, Google 등 다양한 LLM 지원
|
|
32
35
|
- **Fallback 재시도**: 실패 시 자동으로 fallback 모델로 재시도
|
|
33
36
|
|
|
@@ -35,13 +38,13 @@
|
|
|
35
38
|
|
|
36
39
|
```bash
|
|
37
40
|
# npm으로 설치
|
|
38
|
-
npm install @heripo/document-processor @heripo/model
|
|
41
|
+
npm install @heripo/document-processor @heripo/model @heripo/logger
|
|
39
42
|
|
|
40
43
|
# pnpm으로 설치
|
|
41
|
-
pnpm add @heripo/document-processor @heripo/model
|
|
44
|
+
pnpm add @heripo/document-processor @heripo/model @heripo/logger
|
|
42
45
|
|
|
43
46
|
# yarn으로 설치
|
|
44
|
-
yarn add @heripo/document-processor @heripo/model
|
|
47
|
+
yarn add @heripo/document-processor @heripo/model @heripo/logger
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
추가로 LLM 프로파이더의 SDK가 필요합니다:
|
|
@@ -60,7 +63,12 @@ import { anthropic } from '@ai-sdk/anthropic';
|
|
|
60
63
|
import { DocumentProcessor } from '@heripo/document-processor';
|
|
61
64
|
import { Logger } from '@heripo/logger';
|
|
62
65
|
|
|
63
|
-
const logger = Logger(
|
|
66
|
+
const logger = new Logger({
|
|
67
|
+
debug: (...args) => console.debug('[heripo]', ...args),
|
|
68
|
+
info: (...args) => console.info('[heripo]', ...args),
|
|
69
|
+
warn: (...args) => console.warn('[heripo]', ...args),
|
|
70
|
+
error: (...args) => console.error('[heripo]', ...args),
|
|
71
|
+
});
|
|
64
72
|
|
|
65
73
|
// 기본 사용 - fallback 모델만 지정
|
|
66
74
|
const processor = new DocumentProcessor({
|
|
@@ -189,6 +197,50 @@ const { document, usage } = await processor.process(
|
|
|
189
197
|
);
|
|
190
198
|
```
|
|
191
199
|
|
|
200
|
+
### 원천 Docling 참조 보존
|
|
201
|
+
|
|
202
|
+
호출자가 원천 Docling JSON의 저장 위치나 해시를 알고 있다면 `source`로 전달할 수 있습니다. 이 값은 `ProcessedDocument.source`에 그대로 보존됩니다. `validateSourceRefs` 또는 `sourceRefValidationMode`를 사용하면 processor가 생성한 `sourceRef`, `captionSourceRefs`가 입력 `DoclingDocument`에 실제로 존재하는지 검증합니다.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const { document } = await processor.process(
|
|
206
|
+
doclingDocument,
|
|
207
|
+
'report-001',
|
|
208
|
+
artifactDir,
|
|
209
|
+
{
|
|
210
|
+
pageRangeMap,
|
|
211
|
+
tocEntries,
|
|
212
|
+
source: {
|
|
213
|
+
pipelineRunId: 'run-001',
|
|
214
|
+
doclingObjectKey: 'docling/report-001.json',
|
|
215
|
+
doclingSha256: '...',
|
|
216
|
+
handoffManifestObjectKey: 'manifests/run-001.json',
|
|
217
|
+
},
|
|
218
|
+
sourceRefValidationMode: 'warn', // 'off' | 'warn' | 'error'
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
console.log(document.source);
|
|
223
|
+
console.log(document.chapters[0].textBlocks[0].sourceRef);
|
|
224
|
+
console.log(document.images[0].captionSourceRefs);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`ProcessedDocument.source`는 처리에 사용된 Docling artifact 자체를 가리키는 문서 단위 metadata입니다. `TextBlock.sourceRef`, `Chapter.sourceRefs`, `ProcessedImage.sourceRef`, `ProcessedTable.sourceRef`, `ProcessedFootnote.sourceRef`는 해당 processed node가 어떤 Docling node에서 왔는지 가리키는 node-level 참조입니다. 이미지와 테이블의 `captionSourceRefs`는 resource node가 아니라 caption text node의 참조만 담습니다.
|
|
228
|
+
|
|
229
|
+
`sourceRefValidationMode: 'error'`는 누락된 참조가 있으면 처리를 실패시킵니다. `validateSourceRefs: true`는 호환용 단축 옵션이며, 별도 mode를 지정하지 않으면 `'error'`와 같습니다.
|
|
230
|
+
|
|
231
|
+
테이블 셀에는 cell-level `sourceRef`를 만들지 않습니다. 특정 셀의 원천 위치는 `table.sourceRef`와 `grid[row][col]`의 row/column index를 함께 사용해 추적합니다.
|
|
232
|
+
|
|
233
|
+
### 테이블 그리드 처리
|
|
234
|
+
|
|
235
|
+
처리된 테이블은 화면에 보이는 셀만 담은 compact `grid`를 제공합니다. processor는 다음을 수행합니다:
|
|
236
|
+
|
|
237
|
+
- Docling의 row/column span을 `rowSpan`, `colSpan`으로 보존
|
|
238
|
+
- 행/열 헤더를 `isHeader`로 표시
|
|
239
|
+
- Docling이 병합 셀의 덮인 영역을 반복 제공하는 경우 shadow entry 제거
|
|
240
|
+
- Docling의 `data.grid`가 비어 있으면 `table_cells`에서 그리드 구성
|
|
241
|
+
|
|
242
|
+
`numRows`와 `numCols`는 논리적 테이블 크기를 유지합니다. 개별 셀에는 `sourceRef`를 저장하지 않으므로, 셀의 원천 위치를 추적할 때는 `table.sourceRef`와 `grid[row][col]` 위치를 함께 사용합니다.
|
|
243
|
+
|
|
192
244
|
## 처리 파이프라인
|
|
193
245
|
|
|
194
246
|
DocumentProcessor는 다음 5단계 파이프라인으로 문서를 처리합니다:
|
|
@@ -237,7 +289,7 @@ DocumentProcessor는 다음 5단계 파이프라인으로 문서를 처리합니
|
|
|
237
289
|
### 4. 리소스 변환
|
|
238
290
|
|
|
239
291
|
- **이미지**: CaptionParser로 캡션 추출 및 파싱
|
|
240
|
-
- **테이블**: 그리드 데이터
|
|
292
|
+
- **테이블**: 그리드 데이터 변환, 병합 셀 shadow filtering, span 보존 및 캡션 파싱
|
|
241
293
|
- **캡션 검증**: CaptionValidator로 파싱 결과 검증
|
|
242
294
|
|
|
243
295
|
### 5. 챕터 변환 (ChapterConverter)
|
|
@@ -247,7 +299,7 @@ DocumentProcessor는 다음 5단계 파이프라인으로 문서를 처리합니
|
|
|
247
299
|
- 페이지 범위별로 텍스트 블록을 챕터에 연결
|
|
248
300
|
- 이미지/테이블 ID를 적절한 챕터에 연결
|
|
249
301
|
- 각주 ID를 적절한 챕터에 연결
|
|
250
|
-
-
|
|
302
|
+
- TOC 항목이 비어 있으면 TOC 기반 챕터 변환을 진행할 수 없으므로 `TocNotFoundError` 발생
|
|
251
303
|
|
|
252
304
|
## API 문서
|
|
253
305
|
|
|
@@ -257,7 +309,7 @@ DocumentProcessor는 다음 5단계 파이프라인으로 문서를 처리합니
|
|
|
257
309
|
|
|
258
310
|
```typescript
|
|
259
311
|
interface DocumentProcessorOptions {
|
|
260
|
-
logger:
|
|
312
|
+
logger: LoggerMethods; // 로거 인스턴스 (필수)
|
|
261
313
|
|
|
262
314
|
// LLM 모델 설정
|
|
263
315
|
fallbackModel: LanguageModel; // Fallback 모델 (필수)
|
|
@@ -293,6 +345,9 @@ DoclingDocument를 ProcessedDocument로 변환합니다.
|
|
|
293
345
|
interface DocumentProcessorProcessOptions {
|
|
294
346
|
pageRangeMap?: Record<number, PageRange>;
|
|
295
347
|
tocEntries?: TocEntry[];
|
|
348
|
+
source?: ProcessedDocumentSource;
|
|
349
|
+
validateSourceRefs?: boolean;
|
|
350
|
+
sourceRefValidationMode?: 'off' | 'warn' | 'error';
|
|
296
351
|
}
|
|
297
352
|
```
|
|
298
353
|
|
|
@@ -301,7 +356,7 @@ interface DocumentProcessorProcessOptions {
|
|
|
301
356
|
- `doclingDoc` (DoclingDocument): PDF 파서의 출력
|
|
302
357
|
- `reportId` (string): 리포트 ID
|
|
303
358
|
- `artifactDir` (string): `images/`, `pages/`, `result.json` 같은 parser 산출물이 들어 있는 디렉토리
|
|
304
|
-
- `processOptions` (DocumentProcessorProcessOptions, 선택): 문서별 처리 입력값. `pageRangeMap`이 제공되면 자동 페이지 범위 파싱을 건너뜁니다. `tocEntries`가 제공되면 자동 TOC 추출을 건너뜁니다.
|
|
359
|
+
- `processOptions` (DocumentProcessorProcessOptions, 선택): 문서별 처리 입력값. `pageRangeMap`이 제공되면 자동 페이지 범위 파싱을 건너뜁니다. `tocEntries`가 제공되면 자동 TOC 추출을 건너뜁니다. `source`는 원천 Docling artifact metadata를 결과에 보존하고, `sourceRefValidationMode`는 생성된 원천 참조의 검증 방식을 제어합니다.
|
|
305
360
|
|
|
306
361
|
**반환값:**
|
|
307
362
|
|
|
@@ -349,7 +404,7 @@ try {
|
|
|
349
404
|
const { document, usage } = await processor.process(doc, 'id', 'path');
|
|
350
405
|
} catch (error) {
|
|
351
406
|
if (error instanceof TocNotFoundError) {
|
|
352
|
-
console.
|
|
407
|
+
console.error('TOC를 찾을 수 없습니다. 수동 TOC 검수가 필요합니다.');
|
|
353
408
|
} else if (error instanceof TocParseError) {
|
|
354
409
|
console.error('TOC 파싱 실패:', error.message);
|
|
355
410
|
}
|
|
@@ -394,6 +449,13 @@ console.log('토큰 사용:', tokenUsage);
|
|
|
394
449
|
- [@heripo/pdf-parser](../pdf-parser) - PDF 파싱 및 OCR
|
|
395
450
|
- [@heripo/model](../model) - 데이터 모델 및 타입 정의
|
|
396
451
|
|
|
452
|
+
## 후원
|
|
453
|
+
|
|
454
|
+
heripo lab의 오픈소스 연구를 후원하려면 다음 경로를 이용할 수 있습니다:
|
|
455
|
+
|
|
456
|
+
- [Open Collective](https://opencollective.com/heripo-project): 전반적인 프로젝트 후원
|
|
457
|
+
- [fairy.hada.io/@heripo](https://fairy.hada.io/@heripo): 한국인 개인 후원자를 위한 원화 결제
|
|
458
|
+
|
|
397
459
|
## 라이선스
|
|
398
460
|
|
|
399
461
|
이 패키지는 [Apache License 2.0](../../LICENSE) 라이선스 하에 배포됩니다.
|
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [Usage](#usage)
|
|
21
21
|
- [Processing Pipeline](#processing-pipeline)
|
|
22
22
|
- [API Documentation](#api-documentation)
|
|
23
|
+
- [Sponsor](#sponsor)
|
|
23
24
|
- [License](#license)
|
|
24
25
|
|
|
25
26
|
## Key Features
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
- **Hierarchical Structure**: Automatic generation of chapter/section/subsection hierarchy
|
|
29
30
|
- **Page Mapping**: Actual page number mapping using Vision LLM
|
|
30
31
|
- **Caption Parsing**: Automatic parsing of image and table captions
|
|
32
|
+
- **Source Provenance**: Preserves Docling source metadata and node-level references
|
|
33
|
+
- **Table Grid Normalization**: Preserves row/column spans and removes merged-cell shadow entries
|
|
31
34
|
- **LLM Flexibility**: Support for various LLMs including OpenAI, Anthropic, Google
|
|
32
35
|
- **Fallback Retry**: Automatic retry with fallback model on failure
|
|
33
36
|
|
|
@@ -35,13 +38,13 @@
|
|
|
35
38
|
|
|
36
39
|
```bash
|
|
37
40
|
# Install with npm
|
|
38
|
-
npm install @heripo/document-processor @heripo/model
|
|
41
|
+
npm install @heripo/document-processor @heripo/model @heripo/logger
|
|
39
42
|
|
|
40
43
|
# Install with pnpm
|
|
41
|
-
pnpm add @heripo/document-processor @heripo/model
|
|
44
|
+
pnpm add @heripo/document-processor @heripo/model @heripo/logger
|
|
42
45
|
|
|
43
46
|
# Install with yarn
|
|
44
|
-
yarn add @heripo/document-processor @heripo/model
|
|
47
|
+
yarn add @heripo/document-processor @heripo/model @heripo/logger
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
Additionally, LLM provider SDKs are required:
|
|
@@ -60,7 +63,12 @@ import { anthropic } from '@ai-sdk/anthropic';
|
|
|
60
63
|
import { DocumentProcessor } from '@heripo/document-processor';
|
|
61
64
|
import { Logger } from '@heripo/logger';
|
|
62
65
|
|
|
63
|
-
const logger = Logger(
|
|
66
|
+
const logger = new Logger({
|
|
67
|
+
debug: (...args) => console.debug('[heripo]', ...args),
|
|
68
|
+
info: (...args) => console.info('[heripo]', ...args),
|
|
69
|
+
warn: (...args) => console.warn('[heripo]', ...args),
|
|
70
|
+
error: (...args) => console.error('[heripo]', ...args),
|
|
71
|
+
});
|
|
64
72
|
|
|
65
73
|
// Basic usage - specify fallback model only
|
|
66
74
|
const processor = new DocumentProcessor({
|
|
@@ -189,6 +197,66 @@ const { document, usage } = await processor.process(
|
|
|
189
197
|
);
|
|
190
198
|
```
|
|
191
199
|
|
|
200
|
+
### Preserving Source Docling References
|
|
201
|
+
|
|
202
|
+
If the caller knows where the source Docling JSON is stored or has its hash,
|
|
203
|
+
pass that metadata through `source`. The value is preserved as
|
|
204
|
+
`ProcessedDocument.source`. Use `validateSourceRefs` or
|
|
205
|
+
`sourceRefValidationMode` to verify that generated `sourceRef` and
|
|
206
|
+
`captionSourceRefs` values exist in the input `DoclingDocument`.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const { document } = await processor.process(
|
|
210
|
+
doclingDocument,
|
|
211
|
+
'report-001',
|
|
212
|
+
artifactDir,
|
|
213
|
+
{
|
|
214
|
+
pageRangeMap,
|
|
215
|
+
tocEntries,
|
|
216
|
+
source: {
|
|
217
|
+
pipelineRunId: 'run-001',
|
|
218
|
+
doclingObjectKey: 'docling/report-001.json',
|
|
219
|
+
doclingSha256: '...',
|
|
220
|
+
handoffManifestObjectKey: 'manifests/run-001.json',
|
|
221
|
+
},
|
|
222
|
+
sourceRefValidationMode: 'warn', // 'off' | 'warn' | 'error'
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
console.log(document.source);
|
|
227
|
+
console.log(document.chapters[0].textBlocks[0].sourceRef);
|
|
228
|
+
console.log(document.images[0].captionSourceRefs);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`ProcessedDocument.source` is document-level metadata that identifies the
|
|
232
|
+
Docling artifact used for processing. `TextBlock.sourceRef`,
|
|
233
|
+
`Chapter.sourceRefs`, `ProcessedImage.sourceRef`, `ProcessedTable.sourceRef`,
|
|
234
|
+
and `ProcessedFootnote.sourceRef` are node-level references that point back to
|
|
235
|
+
the Docling nodes each processed node came from. For images and tables,
|
|
236
|
+
`captionSourceRefs` contains only caption text node references, not the resource
|
|
237
|
+
node reference.
|
|
238
|
+
|
|
239
|
+
`sourceRefValidationMode: 'error'` fails processing when references are missing.
|
|
240
|
+
`validateSourceRefs: true` is a compatibility shortcut and behaves like
|
|
241
|
+
`'error'` unless a mode is explicitly provided.
|
|
242
|
+
|
|
243
|
+
Table cells do not receive cell-level `sourceRef` values. To locate a specific
|
|
244
|
+
cell in the source artifact, combine `table.sourceRef` with the
|
|
245
|
+
`grid[row][col]` row/column indexes.
|
|
246
|
+
|
|
247
|
+
### Table Grid Handling
|
|
248
|
+
|
|
249
|
+
Processed tables expose a compact `grid` of visible cells. The processor:
|
|
250
|
+
|
|
251
|
+
- Preserves Docling row/column spans as `rowSpan` and `colSpan`
|
|
252
|
+
- Marks row and column headers through `isHeader`
|
|
253
|
+
- Removes merged-cell shadow entries when Docling repeats covered cells
|
|
254
|
+
- Falls back to `table_cells` when Docling's `data.grid` is empty
|
|
255
|
+
|
|
256
|
+
`numRows` and `numCols` keep the logical table size. Individual cells do not
|
|
257
|
+
store `sourceRef`; use `table.sourceRef` together with the `grid[row][col]`
|
|
258
|
+
position when tracing a cell back to the source table.
|
|
259
|
+
|
|
192
260
|
## Processing Pipeline
|
|
193
261
|
|
|
194
262
|
DocumentProcessor processes documents through a 5-stage pipeline:
|
|
@@ -237,7 +305,7 @@ DocumentProcessor processes documents through a 5-stage pipeline:
|
|
|
237
305
|
### 4. Resource Transformation
|
|
238
306
|
|
|
239
307
|
- **Images**: Caption extraction and parsing with CaptionParser
|
|
240
|
-
- **Tables**: Grid data transformation and caption parsing
|
|
308
|
+
- **Tables**: Grid data transformation, merged-cell shadow filtering, span preservation, and caption parsing
|
|
241
309
|
- **Caption Validation**: Parsing result validation with CaptionValidator
|
|
242
310
|
|
|
243
311
|
### 5. Chapter Conversion (ChapterConverter)
|
|
@@ -247,7 +315,7 @@ DocumentProcessor processes documents through a 5-stage pipeline:
|
|
|
247
315
|
- Link text blocks to chapters by page range
|
|
248
316
|
- Connect image/table IDs to appropriate chapters
|
|
249
317
|
- Link footnote IDs to appropriate chapters
|
|
250
|
-
-
|
|
318
|
+
- Throws `TocNotFoundError` when TOC entries are empty because TOC-based chapter conversion is required
|
|
251
319
|
|
|
252
320
|
## API Documentation
|
|
253
321
|
|
|
@@ -257,7 +325,7 @@ DocumentProcessor processes documents through a 5-stage pipeline:
|
|
|
257
325
|
|
|
258
326
|
```typescript
|
|
259
327
|
interface DocumentProcessorOptions {
|
|
260
|
-
logger:
|
|
328
|
+
logger: LoggerMethods; // Logger instance (required)
|
|
261
329
|
|
|
262
330
|
// LLM model settings
|
|
263
331
|
fallbackModel: LanguageModel; // Fallback model (required)
|
|
@@ -293,6 +361,9 @@ Transforms DoclingDocument into ProcessedDocument.
|
|
|
293
361
|
interface DocumentProcessorProcessOptions {
|
|
294
362
|
pageRangeMap?: Record<number, PageRange>;
|
|
295
363
|
tocEntries?: TocEntry[];
|
|
364
|
+
source?: ProcessedDocumentSource;
|
|
365
|
+
validateSourceRefs?: boolean;
|
|
366
|
+
sourceRefValidationMode?: 'off' | 'warn' | 'error';
|
|
296
367
|
}
|
|
297
368
|
```
|
|
298
369
|
|
|
@@ -301,7 +372,7 @@ interface DocumentProcessorProcessOptions {
|
|
|
301
372
|
- `doclingDoc` (DoclingDocument): PDF parser output
|
|
302
373
|
- `reportId` (string): Report ID
|
|
303
374
|
- `artifactDir` (string): Artifact directory containing parser outputs such as `images/`, `pages/`, and `result.json`
|
|
304
|
-
- `processOptions` (DocumentProcessorProcessOptions, optional): Per-document processing inputs. When `pageRangeMap` is provided, automatic page range parsing is skipped. When `tocEntries` is provided, automatic TOC extraction is skipped.
|
|
375
|
+
- `processOptions` (DocumentProcessorProcessOptions, optional): Per-document processing inputs. When `pageRangeMap` is provided, automatic page range parsing is skipped. When `tocEntries` is provided, automatic TOC extraction is skipped. `source` preserves source Docling artifact metadata, and `sourceRefValidationMode` controls generated source reference validation.
|
|
305
376
|
|
|
306
377
|
**Returns:**
|
|
307
378
|
|
|
@@ -349,7 +420,7 @@ try {
|
|
|
349
420
|
const { document, usage } = await processor.process(doc, 'id', 'path');
|
|
350
421
|
} catch (error) {
|
|
351
422
|
if (error instanceof TocNotFoundError) {
|
|
352
|
-
console.
|
|
423
|
+
console.error('TOC not found. Manual TOC review is required.');
|
|
353
424
|
} else if (error instanceof TocParseError) {
|
|
354
425
|
console.error('TOC parsing failed:', error.message);
|
|
355
426
|
}
|
|
@@ -394,6 +465,13 @@ console.log('Token usage:', tokenUsage);
|
|
|
394
465
|
- [@heripo/pdf-parser](../pdf-parser) - PDF parsing and OCR
|
|
395
466
|
- [@heripo/model](../model) - Data models and type definitions
|
|
396
467
|
|
|
468
|
+
## Sponsor
|
|
469
|
+
|
|
470
|
+
If you'd like to support heripo lab's open-source research, you can sponsor us through:
|
|
471
|
+
|
|
472
|
+
- [Open Collective](https://opencollective.com/heripo-project) for general project sponsorship.
|
|
473
|
+
- [fairy.hada.io/@heripo](https://fairy.hada.io/@heripo) for Korean individual supporters who prefer KRW payments.
|
|
474
|
+
|
|
397
475
|
## License
|
|
398
476
|
|
|
399
477
|
This package is distributed under the [Apache License 2.0](../../LICENSE).
|