mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes Adds a PDF/UA converter, an accessibility report, and PDF/A conformance level A. **New: `POST /api/v1/convert/pdf/ua`** (Convert tool, "PDF/UA" target). Tags an untagged PDF, marks decorative content as artifacts, embeds missing fonts and applies the document-level PDF/UA requirements (title, language, tab order, form-field descriptions), then validates with veraPDF. The `pdfuaid` declaration is written only if validation passes, so a returned file never claims more than it delivers; response headers report whether it was declared, how many checks still fail and how many images still need a description. **New: `POST /api/v1/security/accessibility-report`.** Reports what fails, what the converter can fix on its own, what needs a person, and lists the figures needing a description with the keys the conversion accepts back. Read-only; does not modify the file. Capped at 100 MB / 2000 pages and weighted `LARGE_WEIGHT`, since it runs a full veraPDF pass plus the converter's layout analysis over every page. **PDF/A level A.** `pdfa-1a`, `pdfa-2a` and `pdfa-3a` output formats on the existing `/api/v1/convert/pdf/pdfa` endpoint. Level A is level B plus tagging, so the document is tagged after Ghostscript (which discards any structure tree it is given) and the level A claim is written only if veraPDF agrees. Optional `pdfUa=true` additionally declares PDF/UA alongside PDF/A, again only if it validates. Honesty rules the implementation holds to: - **Never claim a level that was not reached.** If tagging fails, the file is returned at level B and is named `_PDFA-2b.pdf`, not `_PDFA-2a.pdf`. With `strict=true` the request fails outright rather than returning a level B file against a level A request, and a level B pass no longer satisfies a strict level A request. - **Never relabel a document's language.** The requested language (default `en-GB`) is applied only when the document declares none; a French PDF stays French unless the caller sets `overrideLanguage`, and ignoring a requested language is reported as a warning. - **Never invent alternative text.** Descriptions come from the caller. The Convert panel can list the images needing one (via the report endpoint) and send them back per figure; any image left undescribed blocks the conformance claim rather than being papered over. - **Never certify hidden content.** Marking images decorative, or suppressing text that could not be tagged reliably, withdraws the claim instead of passing the checker by hiding content. PDF/UA-1 and PDF/UA-2 are both offered; UA-2 raises the file to PDF 2.0 and namespaces the structure tree, and its test asserts conformance rather than merely reporting it. Convert steps saved in Automations/Pipelines round-trip their PDF/UA settings (profile, language, override, title, font embedding, descriptions). --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
279 lines
13 KiB
Python
279 lines
13 KiB
Python
# AUTO-GENERATED FILE. DO NOT EDIT.
|
|
# Generated by scripts/generate_tool_models.py from the Java OpenAPI spec (SwaggerDoc.json).
|
|
# Regenerate with: task engine:tool-models
|
|
"""What each tool endpoint accepts and produces, so a planned chain can be checked before it
|
|
is run. Declared in Java with ``@ToolIO``; see ``stirling.services.tool_io_compat`` for the
|
|
compatibility rules that read this table."""
|
|
|
|
from enum import StrEnum
|
|
|
|
from pydantic import Field
|
|
|
|
from stirling.models.base import ApiModel
|
|
from stirling.models.tool_models import ToolEndpoint
|
|
|
|
|
|
class ToolFormat(StrEnum):
|
|
"""The kind of file a tool consumes or produces. ``ANY`` accepts or produces anything;
|
|
``NONE`` means no file at all, such as a report or a status."""
|
|
|
|
PDF = "PDF"
|
|
PDF_ENCRYPTED = "PDF_ENCRYPTED"
|
|
IMAGE = "IMAGE"
|
|
ZIP = "ZIP"
|
|
WORD = "WORD"
|
|
PPT = "PPT"
|
|
EXCEL = "EXCEL"
|
|
CSV = "CSV"
|
|
HTML = "HTML"
|
|
XML = "XML"
|
|
JSON = "JSON"
|
|
TEXT = "TEXT"
|
|
MARKDOWN = "MARKDOWN"
|
|
JAVASCRIPT = "JAVASCRIPT"
|
|
EBOOK = "EBOOK"
|
|
EMAIL = "EMAIL"
|
|
POSTSCRIPT = "POSTSCRIPT"
|
|
PCL = "PCL"
|
|
XPS = "XPS"
|
|
VIDEO = "VIDEO"
|
|
CBZ = "CBZ"
|
|
CBR = "CBR"
|
|
ANY = "ANY"
|
|
NONE = "NONE"
|
|
|
|
|
|
class ToolArity(StrEnum):
|
|
"""How many files go in and come out (Single/Multiple In, Single/Multiple Out). A
|
|
multi-output tool returns its results zipped, and the caller unpacks them."""
|
|
|
|
SISO = "SISO"
|
|
SIMO = "SIMO"
|
|
MISO = "MISO"
|
|
MIMO = "MIMO"
|
|
|
|
|
|
class ToolIOWhen(ApiModel):
|
|
"""One condition on a request parameter, guarding a :class:`ToolIOCase`."""
|
|
|
|
param: str
|
|
matches: list[str]
|
|
# The value the endpoint uses when this parameter is absent; None when it has none.
|
|
default: str | None = None
|
|
|
|
|
|
class ToolIOCase(ApiModel):
|
|
"""An output that applies when every condition in ``when`` holds."""
|
|
|
|
when: list[ToolIOWhen]
|
|
produces: ToolFormat
|
|
arity: ToolArity
|
|
|
|
|
|
class ToolIOSpec(ApiModel):
|
|
"""What one endpoint accepts and produces."""
|
|
|
|
accepts: list[ToolFormat]
|
|
produces: ToolFormat
|
|
arity: ToolArity
|
|
cases: list[ToolIOCase] = Field(default_factory=list)
|
|
|
|
|
|
TOOL_IO: dict[ToolEndpoint, ToolIOSpec] = {
|
|
ToolEndpoint.CBR_TO_PDF: ToolIOSpec(accepts=[ToolFormat.CBR], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.CBZ_TO_PDF: ToolIOSpec(accepts=[ToolFormat.CBZ], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.EBOOK_TO_PDF: ToolIOSpec(accepts=[ToolFormat.EBOOK], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.EML_TO_PDF: ToolIOSpec(accepts=[ToolFormat.EMAIL], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.FILE_TO_PDF: ToolIOSpec(accepts=[ToolFormat.ANY], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.HTML_TO_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.HTML, ToolFormat.ZIP], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.IMG_TO_PDF: ToolIOSpec(accepts=[ToolFormat.IMAGE], produces=ToolFormat.PDF, arity=ToolArity.MISO),
|
|
ToolEndpoint.MARKDOWN_TO_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.MARKDOWN, ToolFormat.ZIP], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.PDF_TO_CBR: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.CBR, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_CBZ: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.CBZ, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_CSV: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.CSV, arity=ToolArity.SIMO),
|
|
ToolEndpoint.PDF_TO_EPUB: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.EBOOK, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_HTML: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.ZIP, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_IMG: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.IMAGE,
|
|
arity=ToolArity.SIMO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="singleOrMultiple", matches=["single"], default="multiple")],
|
|
produces=ToolFormat.IMAGE,
|
|
arity=ToolArity.SISO,
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.PDF_TO_MARKDOWN: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.MARKDOWN, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.PDF_TO_PDFA: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_PRESENTATION: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PPT, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.PDF_TO_TEXT: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.TEXT,
|
|
arity=ToolArity.SISO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="outputFormat", matches=["rtf"])], produces=ToolFormat.WORD, arity=ToolArity.SISO
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.PDF_TO_UA: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_VECTOR: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.IMAGE,
|
|
arity=ToolArity.SISO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="outputFormat", matches=["ps"], default="eps")],
|
|
produces=ToolFormat.POSTSCRIPT,
|
|
arity=ToolArity.SISO,
|
|
),
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="outputFormat", matches=["pcl"], default="eps")],
|
|
produces=ToolFormat.PCL,
|
|
arity=ToolArity.SISO,
|
|
),
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="outputFormat", matches=["xps"], default="eps")],
|
|
produces=ToolFormat.XPS,
|
|
arity=ToolArity.SISO,
|
|
),
|
|
],
|
|
),
|
|
ToolEndpoint.PDF_TO_WORD: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.WORD, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_XLSX: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.EXCEL, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_XML: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.XML, arity=ToolArity.SISO),
|
|
ToolEndpoint.SVG_TO_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.IMAGE],
|
|
produces=ToolFormat.PDF,
|
|
arity=ToolArity.MIMO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="combineIntoSinglePdf", matches=["true"], default="false")],
|
|
produces=ToolFormat.PDF,
|
|
arity=ToolArity.MISO,
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.URL_TO_PDF: ToolIOSpec(accepts=[ToolFormat.NONE], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.VECTOR_TO_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.POSTSCRIPT], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.BOOKLET_IMPOSITION: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.CROP: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.EDIT_TABLE_OF_CONTENTS: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.EDIT_TEXT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.MERGE_PDFS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.MISO),
|
|
ToolEndpoint.MULTI_PAGE_LAYOUT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.PDF_TO_SINGLE_PAGE: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.REARRANGE_PAGES: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REMOVE_IMAGE_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REMOVE_PAGES: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.ROTATE_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.SCALE_PAGES: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.SPLIT_BY_SIZE_OR_COUNT: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO
|
|
),
|
|
ToolEndpoint.SPLIT_FOR_POSTER_PRINT: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO
|
|
),
|
|
ToolEndpoint.SPLIT_PAGES: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO),
|
|
ToolEndpoint.SPLIT_PDF_BY_CHAPTERS: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO
|
|
),
|
|
ToolEndpoint.SPLIT_PDF_BY_SECTIONS: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO
|
|
),
|
|
ToolEndpoint.ADD_COMMENTS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.ADD_PAGE_NUMBERS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.ADD_STAMP: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.AUTO_RENAME: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.AUTO_ROTATE_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.PDF,
|
|
arity=ToolArity.SISO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="dryRun", matches=["true"], default="false")],
|
|
produces=ToolFormat.JSON,
|
|
arity=ToolArity.SISO,
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.AUTO_SPLIT_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO),
|
|
ToolEndpoint.COMPRESS_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.DELETE_ATTACHMENT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.EXTRACT_ATTACHMENTS: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.ZIP, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.EXTRACT_IMAGE_SCANS: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.IMAGE, arity=ToolArity.SIMO
|
|
),
|
|
ToolEndpoint.EXTRACT_IMAGES: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.IMAGE, arity=ToolArity.SIMO),
|
|
ToolEndpoint.FLATTEN: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.OCR_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.PDF,
|
|
arity=ToolArity.SISO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[ToolIOWhen(param="sidecar", matches=["true"], default="false")],
|
|
produces=ToolFormat.ZIP,
|
|
arity=ToolArity.SISO,
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.REMOVE_BLANKS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SIMO),
|
|
ToolEndpoint.RENAME_ATTACHMENT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REPAIR: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REPLACE_INVERT_PDF: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.SCANNER_EFFECT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.UNLOCK_PDF_FORMS: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.UPDATE_METADATA: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.ACCESSIBILITY_REPORT: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF], produces=ToolFormat.JSON, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.ADD_PASSWORD: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF],
|
|
produces=ToolFormat.PDF_ENCRYPTED,
|
|
arity=ToolArity.SISO,
|
|
cases=[
|
|
ToolIOCase(
|
|
when=[
|
|
ToolIOWhen(param="password", matches=[""], default=""),
|
|
ToolIOWhen(param="ownerPassword", matches=[""], default=""),
|
|
],
|
|
produces=ToolFormat.PDF,
|
|
arity=ToolArity.SISO,
|
|
)
|
|
],
|
|
),
|
|
ToolEndpoint.ADD_WATERMARK: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.AUTO_REDACT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REDACT: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REDACT_EXECUTE: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REMOVE_CERT_SIGN: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.REMOVE_PASSWORD: ToolIOSpec(
|
|
accepts=[ToolFormat.PDF, ToolFormat.PDF_ENCRYPTED], produces=ToolFormat.PDF, arity=ToolArity.SISO
|
|
),
|
|
ToolEndpoint.SANITIZE_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
ToolEndpoint.TIMESTAMP_PDF: ToolIOSpec(accepts=[ToolFormat.PDF], produces=ToolFormat.PDF, arity=ToolArity.SISO),
|
|
}
|