census
What is inside a deck, as plain JSON: parts, features, namespaces and problems.
Tells you what is inside a deck as plain JSON - parts, features, namespaces, problems - without refusing a broken file.
npm install @pptx-studio/census @pptx-studio/opc @pptx-studio/xml
What it is
censusPackage opens a deck and returns one JSON-safe object describing it: the archive, every part with its size and element count, the relationship graph, the presentation structure, a feature census keyed to the phase of the plan that draws each feature, the namespaces actually used, and any problems it met on the way.
It refuses nothing. A broken deck still gets a count, with the problem recorded beside it, which is what makes it the right thing to attach to a bug report.
30-second example
// census.worker.ts
self.onmessage = ({ data }: MessageEvent<ArrayBuffer>) => {
const census = censusPackage(new Uint8Array(data), {
onProgress: (done, total) => self.postMessage({ progress: { done, total } }),
});
self.postMessage({ census }); // plain JSON by design, so it survives postMessage
};Key concepts
Plain JSON on purpose
No Map, no class instance, nothing that would not survive postMessage. The census is meant to be computed in a Web Worker and handed back, and the CLI's inspect prints the very same object.
Features by namespace, never by prefix
A chart is recognised by the namespace its element is bound to, not by the c: prefix a writer happened to choose, so a deck that spells its prefixes differently counts the same.
Each feature names its phase
Every feature in the census carries the sub-phase of the plan that renders it, so the answer to "will this deck draw?" is in the census itself.
API reference
32 exports of @pptx-studio/census 0.1.2, read from the package this site installed. Click a name for its declaration.
src/types.ts
NameCounttypeA{ name, count }histogram entry. Sorted by count descending, then name.interface NameCount { readonly name: string; readonly count: number; }ArchiveCensustypeHow the archive itself is put together, before anything is inflated.interface ArchiveCensus { /** The `.pptx` as read: compressed, headers and all. */ readonly bytes: number; readonly entries: number; /** Entries that are OPC parts. The content-type stream is not one. */ readonly parts: number; /** * Entries whose name ends in a slash. * * PowerPoint writes none and our writer never will. A package that has them * still opens, so this is a note rather than a problem - but it is the * clearest single signal that a deck was rebuilt by a third-party tool. */ readonly directoryEntries: number; readonly storedEntries: number; readonly deflatedEntries: number; /** * True when the archive carries a ZIP64 end-of-central-directory record. * * PowerPoint never writes one and our writer never will, but other producers * emit one even for small archives - so this says which producer wrote the * file, not how big it is. */ readonly zip64: boolean; /** Entries carrying a streaming data descriptor rather than sizes up front. */ readonly dataDescriptorEntries: number; /** Sum of every entry's uncompressed size, as the central directory declares. */ readonly declaredInflatedBytes: number; readonly compressedBytes: number; readonly largestPart: NameCount | null; }PartKindtypetype PartKind = 'xml' | 'binary';
PartCensustypeOne part, and what a scan of it found. Ordered as the archive stores them.interface PartCensus { readonly name: string; readonly contentType: string; /** Where the content type came from: a `Default` extension or an `Override`. */ readonly contentTypeOrigin: 'default' | 'override' | 'unknown'; readonly kind: PartKind; readonly bytes: number; readonly compressedBytes: number; /** Elements in the part. `null` for a part that was not scanned. */ readonly elements: number | null; /** Deepest element nesting. `null` for a part that was not scanned. */ readonly maxDepth: number | null; /** Outbound relationships declared in this part's own relationship part. */ readonly relationships: number; }DanglingRelationshiptypeinterface DanglingRelationship { readonly source: string; readonly id: string; readonly target: string; }RelationshipCensustypeinterface RelationshipCensus { readonly total: number; readonly internal: number; readonly external: number; /** Relationship type histogram, shortened to the last path segment. */ readonly byType: readonly NameCount[]; /** Edges whose target part is not in the package. PowerPoint tolerates these. */ readonly dangling: readonly DanglingRelationship[]; /** Parts nothing relates to. Harmless, but they are what a media GC would take. */ readonly unreachableParts: readonly string[]; }EmbeddedFontCensustypeAn embedded font, asp:embeddedFontLstdeclares it.interface EmbeddedFontCensus { readonly typeface: string; readonly panose: string | null; readonly charset: number | null; readonly pitchFamily: number | null; /** Which of regular/bold/italic/boldItalic carry a relationship. */ readonly slots: readonly string[]; }PresentationCensustypeinterface PresentationCensus { readonly slideWidthEmu: number | null; readonly slideHeightEmu: number | null; /** `p:sldSz/@type`, for instance `screen16x9`. */ readonly slideSizeType: string | null; readonly notesWidthEmu: number | null; readonly notesHeightEmu: number | null; readonly slides: number; readonly slideLayouts: number; readonly slideMasters: number; readonly notesSlides: number; readonly notesMasters: number; readonly handoutMasters: number; /** * `p:presentation/@embedTrueTypeFonts`. * * Omit it and PowerPoint ignores a perfect `p:embeddedFontLst` entirely, so * the flag and the list are reported separately on purpose: a deck with fonts * and no flag is a bug someone shipped, not a deck without fonts. */ readonly embedTrueTypeFonts: boolean; /** `p:presentation/@saveSubsetFonts`. Governs subsetting, never compression. */ readonly saveSubsetFonts: boolean; readonly embeddedFonts: readonly EmbeddedFontCensus[]; /** `p14:sectionLst` entries. */ readonly sections: number; readonly customShows: number; readonly firstSlideNum: number | null; readonly rtl: boolean; }TextCensustypeinterface TextCensus { readonly paragraphs: number; readonly runs: number; readonly characters: number; /** `a:fld` - dates, slide numbers, and the GUIDs that must not be regenerated. */ readonly fields: number; readonly hardBreaks: number; readonly bulletChar: number; readonly bulletAutoNum: number; readonly bulletBlip: number; /** `a:normAutofit` - text shrunk to fit, at the scale PowerPoint chose. */ readonly normAutofit: number; /** `a:spAutoFit` - the shape grows instead. */ readonly shapeAutofit: number; }NamespaceCensustypeA namespace as it was actually used, with every prefix it was spelled with.interface NamespaceCensus { readonly uri: string; /** Elements and attributes in this namespace, across every part. */ readonly count: number; readonly prefixes: readonly string[]; /** True when ECMA-376 itself defines the namespace. */ readonly standard: boolean; /** True for a post-ECMA-376 extension namespace we recognise by name. */ readonly extension: boolean; /** True when it appeared in an `mc:Choice` requirement. */ readonly required: boolean; /** True when some `mc:Ignorable` named it. */ readonly ignorable: boolean; }FeatureCensustypeA detected feature, and the phase of the plan that makes it render.interface FeatureCensus { readonly key: string; readonly label: string; readonly count: number; /** The sub-phase or phase that implements it, for instance `4.6` or `10.2`. */ readonly phase: string; }ProblemSeveritytypetype ProblemSeverity = 'error' | 'warning' | 'note';
CensusProblemtypeinterface CensusProblem { readonly severity: ProblemSeverity; readonly code: string; readonly message: string; readonly part: string | null; }CensusTimingstypeinterface CensusTimings { /** Opening the archive: end-of-central-directory, entry table, content types. */ readonly openMs: number; /** Inflating every XML part. */ readonly inflateMs: number; /** Tokenizing and counting. */ readonly scanMs: number; readonly totalMs: number; /** XML bytes handed to the tokenizer, so throughput is derivable. */ readonly xmlBytesScanned: number; readonly partsScanned: number; }PackageCensustypeinterface PackageCensus { /** Schema version of this document, so a stored census can be re-read later. */ readonly censusVersion: 1; readonly archive: ArchiveCensus; readonly parts: readonly PartCensus[]; readonly contentTypes: readonly NameCount[]; readonly relationships: RelationshipCensus; readonly presentation: PresentationCensus | null; readonly features: readonly FeatureCensus[]; readonly namespaces: readonly NamespaceCensus[]; /** The most common qualified element names, spelled as they were written. */ readonly elements: readonly NameCount[]; /** `a:prstGeom/@prst` - which of the 187 presets this deck actually uses. */ readonly presetGeometry: readonly NameCount[]; /** Every typeface named by `a:latin`, `a:ea`, `a:cs` or `a:sym`. */ readonly typefaces: readonly NameCount[]; /** Every `@lang` seen on a run. */ readonly languages: readonly NameCount[]; readonly text: TextCensus; readonly problems: readonly CensusProblem[]; readonly timings: CensusTimings; }
src/census.ts
CensusOptionstypeWhat is inside this.pptx.interface CensusOptions { /** Passed to the ZIP reader. Defaults hold a 200 MB / 1 GB envelope. */ readonly limits?: Partial<ZipLimits> | undefined; readonly tokenizerLimits?: XmlTokenizerLimits | undefined; /** * Called before each part is scanned. * * A 200 MB deck takes long enough that a progress bar is the difference * between "working" and "hung", and the worker has nothing else to report. */ readonly onProgress?: ((done: number, total: number, part: string) => void) | undefined; /** Injectable clock, so a test can assert on timings without a real one. */ readonly now?: (() => number) | undefined; }censusPackagefunctionfunction censusPackage(bytes: Uint8Array, options?: CensusOptions): PackageCensus;
src/format.ts
FormatOptionstypeA census as text.interface FormatOptions { /** Include the full per-part table. Off by default: a real deck has hundreds. */ readonly parts?: boolean | undefined; /** Include the namespace histogram. */ readonly namespaces?: boolean | undefined; /** Rows to show in each histogram before truncating. */ readonly top?: number | undefined; }humanBytesfunctionSizes a human reads. Deliberately binary units, and always labelled.function humanBytes(bytes: number): string;
formatCensusfunctionfunction formatCensus(census: PackageCensus, options?: FormatOptions): string;
src/namespaces.ts
OOXML_NSconstECMA-376 namespaces: the standard vocabulary.const OOXML_NS: { /** DrawingML main. Shapes, colours, text, geometry. */ readonly a: "http://schemas.openxmlformats.org/drawingml/2006/main"; /** PresentationML main. */ readonly p: "http://schemas.openxmlformats.org/presentationml/2006/main"; /** Relationship references *inside* part XML - `r:id`, `r:embed`, `r:link`. */ readonly r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; /** Markup Compatibility and Extensibility. */ readonly mc: "http://schemas.openxmlformats.org/markup-compatibility/2006"; /** DrawingML charts. */ readonly c: "http://schemas.openxmlformats.org/drawingml/2006/chart"; /** SmartArt data, layout, colours and quick styles. */ readonly dgm: "http://schemas.openxmlformats.org/drawingml/2006/diagram"; /** The `p:pic` wrapper used inside a `a:graphicFrame`. */ readonly pic: "http://schemas.openxmlformats.org/drawingml/2006/picture"; /** SpreadsheetML - reachable through an embedded chart workbook. */ readonly x: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; /** WordprocessingML - reachable through an embedded document. */ readonly w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; /** Office Math. */ readonly m: "http://schemas.openxmlformats.org/officeDocument/2006/math"; /** `docProps/app.xml`. */ readonly extendedProperties: "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; /** `docProps/core.xml`. */ readonly coreProperties: "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; /** The `a:lockedCanvas` wrapper. */ readonly lockedCanvas: "http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"; };EXTENSION_NSconstMicrosoft extension namespaces, and the two VML ones.const EXTENSION_NS: { /** DrawingML 2010: `a14:imgProps`, `a14:hiddenFill`, `a14:cameraTool`. */ readonly a14: "http://schemas.microsoft.com/office/drawing/2010/main"; /** DrawingML 2014: `a16:creationId`, `a16:rowId`. */ readonly a16: "http://schemas.microsoft.com/office/drawing/2014/main"; /** PowerPoint 2010: media trim, sections, `p14:creationId`. */ readonly p14: "http://schemas.microsoft.com/office/powerpoint/2010/main"; /** PowerPoint 2012: `p15:sldGuideLst`, `p15:notesGuideLst`, threaded comments. */ readonly p15: "http://schemas.microsoft.com/office/powerpoint/2012/main"; /** PowerPoint 2015. */ readonly p16: "http://schemas.microsoft.com/office/powerpoint/2015/main"; /** The older PowerPoint extension namespace, still present in real decks. */ readonly p2007: "http://schemas.microsoft.com/office/powerpoint/2007/7/12/main"; /** Chart 2007. */ readonly c14: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"; /** Chart 2014. */ readonly c16: "http://schemas.microsoft.com/office/drawing/2014/chart"; /** ChartEx: waterfall, funnel, treemap, sunburst, histogram, box-whisker, map. */ readonly cx: "http://schemas.microsoft.com/office/drawing/2014/chartex"; /** Chart styles - `style1.xml`. */ readonly chartStyle: "http://schemas.microsoft.com/office/drawing/2012/chartStyle"; /** The SmartArt *drawing* fallback: `drawingN.xml`. */ readonly dsp: "http://schemas.microsoft.com/office/drawing/2008/diagram"; /** SmartArt 2016 extensions. */ readonly dgm1612: "http://schemas.microsoft.com/office/drawing/2016/12/diagram"; /** Scalable vector blips - `asvg:svgBlip`. */ readonly asvg: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"; /** 3-D models - `am3d:model3d`. */ readonly am3d: "http://schemas.microsoft.com/office/drawing/2017/model3d"; /** The decorative marker screen readers are meant to honour. */ readonly adec: "http://schemas.microsoft.com/office/drawing/2017/decorative"; /** Ink annotations wrapper. */ readonly aink: "http://schemas.microsoft.com/office/drawing/2016/ink"; /** W3C InkML: the trace data itself. */ readonly inkml: "http://www.w3.org/2003/InkML"; /** VML, still the carrier for OLE object previews. */ readonly v: "urn:schemas-microsoft-com:vml"; /** The VML companion namespace. */ readonly o: "urn:schemas-microsoft-com:office:office"; };CONVENTIONAL_PREFIXconstPrefix conventionally used for a URI, for display only. Never for matching.const CONVENTIONAL_PREFIX: ReadonlyMap<string, string>;
isStandardNamespacefunctionTrue for a namespace that ECMA-376 itself defines.function isStandardNamespace(uri: string): boolean;
isExtensionNamespacefunctionTrue for a namespace that postdates ECMA-376 - the extension surface.function isExtensionNamespace(uri: string): boolean;
src/features.ts
FeatureRuletypeWhat a census looks for, and which phase of the plan makes it render.interface FeatureRule { readonly key: string; readonly label: string; /** Sub-phase that implements it, or `preserve` for carry-across-only. */ readonly phase: string; readonly uri: string; readonly local: string; }FEATURE_RULESconstconst FEATURE_RULES: readonly FeatureRule[];
PART_FEATURE_RULESconstFeatures detected from the package rather than from any element.const PART_FEATURE_RULES: readonly { readonly key: string; readonly label: string; readonly phase: string; readonly match: (partName: string, contentType: string) => boolean; }[];
src/scan.ts
ScanTotalstypeEverything a census accumulates, across every part.interface ScanTotals { readonly elements: Map<string, number>; readonly namespaces: Map<string, NamespaceUse>; readonly features: Map<string, number>; readonly presetGeometry: Map<string, number>; readonly typefaces: Map<string, number>; readonly languages: Map<string, number>; paragraphs: number; runs: number; characters: number; fields: number; hardBreaks: number; bulletChar: number; bulletAutoNum: number; bulletBlip: number; normAutofit: number; shapeAutofit: number; /** Total elements across every part scanned. */ totalElements: number; /** Bindings already built, so every part in the package shares the objects. */ readonly bindings: Map<string, Binding>; }createTotalsfunctionfunction createTotals(): ScanTotals;
PartScantypeWhat one part contributed, for the per-part table.interface PartScan { readonly elements: number; readonly maxDepth: number; /** * Why the part is not well formed, or `null`. * * A token stream is not a tree, so nothing in the tokenizer objects to * `<a><b></a>` or to a document that simply stops - it hands over the tokens * it found and falls silent. The first version of this scanner therefore * reported a truncated slide as perfectly healthy, which is precisely the * wrong answer from a tool whose whole job is to describe a file that is * already suspect. Balance is checked here, against a stack of open names, * because this is the only place that has one. */ readonly imbalance: string | null; }scanXmlPartfunctionScan one XML part, folding what it holds intototals.function scanXmlPart(source: string, totals: ScanTotals, limits?: XmlTokenizerLimits): PartScan;
Errors
This package declares no error class of its own; what it throws comes from the packages beneath it.
Try it live
Reading the deck…