xml
A byte-preserving XML tokenizer, tree, serializer and editor for OOXML parts.
Parses OOXML into a tree that remembers its own bytes, so an edit changes only what you touched and undo is exact.
npm install @pptx-studio/xml
What it is
The XML inside a .pptx has to come back out exactly as it went in, prefixes and whitespace included, or PowerPoint may repair the file. Browsers' DOMParser cannot promise that. @pptx-studio/xml parses into a tree where every node remembers the bytes it came from, and a node nobody edited re-emits by slicing the original buffer.
Edits are operations that hand back their own inverse, so undo restores the document rather than a copy of it. Schema order, Markup Compatibility and extLst are handled here too.
30-second example
const doc = parseXml(store.read('/ppt/slides/slide1.xml'));
const cNvPr = [...descendantElements(doc.root)].find((el) => el.qname === 'p:cNvPr');
if (cNvPr === undefined) throw new Error('no shape');
const undo = applyEdit({ kind: 'setAttribute', element: cNvPr, qname: 'name', value: 'Renamed' });
serializeXml(doc); // the original bytes, except the value of that one attribute
applyEdit(undo);
serializeXml(doc); // byte-identical to what was readKey concepts
Spans are the truth, values are derived
An element knows where it starts and ends in the source. Only a node marked dirty by an edit is re-serialised; everything else is copied. That is what makes the byte-identity check on the markup demo hold for every part of a deck.
Undo is exact, not equivalent
applyEdit returns the edit that undoes it, including which dirty flags it set, so applying the inverse puts the tree back to the state it was in - not to a state that serialises the same.
Where a new child goes
Elements have an order the schema fixes. insertInOrder uses the generated element-order table for the four DrawingML namespaces, so an inserted child lands where PowerPoint expects it.
Markup Compatibility is read, never rewritten
mc:AlternateContent, Ignorable and MustUnderstand are walked for the effective content; the branches themselves are preserved untouched, because the future PowerPoint that reads them is not this code.
API reference
129 exports of @pptx-studio/xml 0.1.2, read from the package this site installed. Click a name for its declaration.
src/mce/namespaces.ts
NSconstThe namespace vocabulary the tokenizer and serializer are written against.const NS: { /** Bound to the `xml:` prefix implicitly; never declared. */ readonly xml: "http://www.w3.org/XML/1998/namespace"; /** The namespace of `xmlns` declarations themselves. */ readonly xmlns: "http://www.w3.org/2000/xmlns/"; /** Markup Compatibility and Extensibility (`mc:`). */ readonly mc: "http://schemas.openxmlformats.org/markup-compatibility/2006"; /** DrawingML main (`a:`). */ readonly a: "http://schemas.openxmlformats.org/drawingml/2006/main"; /** PresentationML main (`p:`). */ readonly p: "http://schemas.openxmlformats.org/presentationml/2006/main"; /** Relationship references inside part XML (`r:`). */ readonly r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; };NamespacePrefixtypetype NamespacePrefix = keyof typeof NS;
NamespaceUritypetype NamespaceUri = (typeof NS)[NamespacePrefix];
XML_SPACE_PRESERVEconstxml:space="preserve".const XML_SPACE_PRESERVE = "preserve";
src/errors.ts
XML_ERROR_CODESconstMachine-readable failure reasons.const XML_ERROR_CODES: readonly ["ERR_DOCTYPE_FORBIDDEN", "ERR_MALFORMED_XML", "ERR_MISMATCHED_TAG", "ERR_UNCLOSED_TAG", "ERR_ROOT_ELEMENT", "ERR_DUPLICATE_ATTRIBUTE", "ERR_UNKNOWN_ENTITY", "ERR_INVALID_CHARACTER", "ERR_UNSUPPORTED_ENCODING", "ERR_LIMIT_EXCEEDED", "ERR_INVALID_MCE", "ERR_SCHEMA_ORDER", "ERR_INVALID_EDIT"];
XmlErrorCodetypetype XmlErrorCode = (typeof XML_ERROR_CODES)[number];
XmlErrorDetailtypeStructured context for a failure. Every field is optional and diagnostic.interface XmlErrorDetail { /** Offset into the decoded source, in UTF-16 code units, when positional. */ readonly offset?: number; /** * The element, attribute or entity name the failure is attributable to. * * Explicitly nullable rather than merely optional: under * `exactOptionalPropertyTypes` a caller that has a `string | undefined` in * hand cannot pass it to an optional-only field, and every caller here does. */ readonly name?: string | undefined; /** The limit that was exceeded. */ readonly limit?: number; /** The value that exceeded it. */ readonly actual?: number; /** Whatever a dependency threw, preserved rather than swallowed. */ readonly cause?: unknown; }XmlErrorclassclass XmlError extends Error { readonly code: XmlErrorCode; readonly detail: XmlErrorDetail; constructor(code: XmlErrorCode, message: string, detail?: XmlErrorDetail); }isXmlErrorfunctionfunction isXmlError(value: unknown): value is XmlError;
positionOffunctionA 1-based line and column for an offset, computed on demand.function positionOf(source: string, offset: number): { line: number; column: number; };
src/parse/chars.ts
isXmlWhitespacefunctionXML 1.0 §2.3S: space, tab, carriage return, line feed. Nothing else.function isXmlWhitespace(code: number): boolean;
isNameStartCharfunctionXML 1.0 Fifth Edition §2.3NameStartChar.function isNameStartChar(code: number): boolean;
isNameCharfunctionXML 1.0 Fifth Edition §2.3NameChar-NameStartCharplus these.function isNameChar(code: number): boolean;
isXmlCharfunctionXML 1.0 §2.2Char, as a predicate over a code unit of already-decoded text.function isXmlChar(code: number): boolean;
isUnpairedSurrogatefunctionTrue if the code unit atindexis half of a surrogate pair with nothing on the other side of it.function isUnpairedSurrogate(source: string, index: number): boolean;
isXmlCodePointfunctionXML 1.0 §2.2Char, as a predicate over a full code point.function isXmlCodePoint(code: number): boolean;
normalizeLineEndingsfunctionXML 1.0 §2.11 end-of-line handling: a processor must behave as though every\r\nand every lone\rin the input were a single\n.function normalizeLineEndings(text: string): string;
isAllWhitespacefunctionTrue if the run contains onlyScharacters.function isAllWhitespace(source: string, start: number, end: number): boolean;
src/parse/source.ts
XmlEncodingtypeEncodings this package can decode losslessly.type XmlEncoding = 'utf-8';
XmlSourcetypeA decoded part, plus what is needed to encode it back byte-identically.interface XmlSource { /** * The decoded document, **including a leading U+FEFF if the bytes carried a * byte order mark**. Every span in the tree indexes into this string. */ readonly text: string; readonly encoding: XmlEncoding; /** True if `text` begins with U+FEFF because the bytes began with EF BB BF. */ readonly bom: boolean; /** Length of the original byte sequence, for diagnostics and budgeting. */ readonly byteLength: number; }decodeXmlSourcefunctionDecode a part into the string the tokenizer scans.function decodeXmlSource(bytes: Uint8Array): XmlSource;
encodeXmlSourcefunctionEncode a source string back to bytes.function encodeXmlSource(text: string): Uint8Array;
src/mce/references.ts
decodeReferencefunctionDecode the reference starting atsource[start], which must be&.function decodeReference(source: string, start: number, end?: number): { readonly text: string; readonly end: number; };decodeCharacterDatafunctionExpand every reference in a run of character data, and apply XML 1.0 §2.11 end-of-line normalization to the literal text around them.function decodeCharacterData(source: string, start: number, end: number): string;
normalizeAttributeValuefunctionXML 1.0 §3.3.3 attribute-value normalization, for the CDATA case - which is every attribute we will ever see, because declaring an attribute as anything else requires a DTD.function normalizeAttributeValue(source: string, start: number, end: number): string;
isLiteralRunfunctionTrue if the run needs no transformation at all, so callers can hand back a plain slice.function isLiteralRun(source: string, start: number, end: number, attribute: boolean): boolean;
escapeTextfunctionEscape a value for use as character data.function escapeText(value: string, at?: number): string;
escapeAttributeValuefunctionEscape a value for use insidequotedelimiters.function escapeAttributeValue(value: string, quote: '"' | "'", at?: number): string;
src/parse/tokenizer.ts
XmlTokenTypetypeA pull tokenizer over a decoded XML document.type XmlTokenType = 'declaration' | 'processingInstruction' | 'comment' | 'startTag' | 'emptyElementTag' | 'endTag' | 'text' | 'cdata';
XAttributetypeOne attribute, with everything needed to reproduce it character for character.interface XAttribute { qname: string; /** Empty string when the name is unprefixed. */ prefix: string; local: string; /** * Start of this attribute's **leading whitespace**, not of its name. * * Including the whitespace is what lets sub-phase 0.6 rewrite one attribute * of a tag and leave the spacing of the others alone. Office's own writer is * consistent, but 70 822 of the 98 777 self-closing tags in our corpus come * from a producer that writes `<a:off x="0" y="0" />` with a space before the * slash, so "the spacing is always the same" is false in practice. */ readonly start: number; readonly nameStart: number; readonly nameEnd: number; /** First character inside the opening quote. */ readonly valueStart: number; /** The closing quote. */ readonly valueEnd: number; /** One past the closing quote. */ readonly end: number; quote: '"' | "'"; /** * Normalized per XML 1.0 §3.3.3. * * Deliberately not the same string as `source.slice(valueStart, valueEnd)`. * `name="a	b"` and `name="a<TAB>b"` have the same normalized value only if * you get §3.3.3 wrong: a literal tab becomes a space, a character reference * to a tab stays a tab. */ value: string; /** * Set by `markAttributeDirty` when the value no longer matches the source. * * While it is false the attribute re-emits by slicing `[start, end)`, which * keeps its original spacing and quote character. Setting it by hand without * marking the owning element is a silent no-op - the element would still be * clean and would re-emit the whole start tag as a slice. */ dirty: boolean; }XmlDeclarationTokentypeinterface XmlDeclarationToken extends TokenBase { readonly type: 'declaration'; readonly version: string; readonly encoding: string | undefined; readonly standalone: string | undefined; }XmlProcessingInstructionTokentypeinterface XmlProcessingInstructionToken extends TokenBase { readonly type: 'processingInstruction'; readonly target: string; /** Everything between the target and `?>`, verbatim. */ readonly data: string; }XmlCommentTokentypeinterface XmlCommentToken extends TokenBase { readonly type: 'comment'; /** The text between `<!--` and `-->`, with line endings normalized. */ readonly value: string; }XmlElementTokentypeinterface XmlElementToken extends TokenBase { readonly type: 'startTag' | 'emptyElementTag'; readonly qname: string; readonly prefix: string; readonly local: string; readonly nameEnd: number; /** * Where the whitespace before `>` or `/>` begins. * * Equal to the last attribute's `end` (or to `nameEnd`) when there is none. */ readonly trailingSpaceStart: number; readonly attributes: readonly XAttribute[]; /** * True if any attribute is an `xmlns` or `xmlns:*` declaration. * * Computed here because the attribute loop has already looked at every name. * It turns `resolvePrefix`'s walk from "scan every ancestor's whole attribute * list" into "step over ancestors that declare nothing", which is the * difference between 37 seconds and milliseconds on a deep tree whose * elements carry many non-namespace attributes. */ readonly hasNamespaceDeclarations: boolean; }XmlEndTagTokentypeinterface XmlEndTagToken extends TokenBase { readonly type: 'endTag'; readonly qname: string; readonly prefix: string; readonly local: string; }XmlTextTokentypeinterface XmlTextToken extends TokenBase { readonly type: 'text'; /** References expanded and line endings normalized. */ readonly value: string; /** True if the run is `S` characters only - inter-element formatting. */ readonly whitespaceOnly: boolean; }XmlCdataTokentypeinterface XmlCdataToken extends TokenBase { readonly type: 'cdata'; /** The text between `<![CDATA[` and `]]>`, with line endings normalized. */ readonly value: string; }XmlTokentypetype XmlToken = XmlDeclarationToken | XmlProcessingInstructionToken | XmlCommentToken | XmlElementToken | XmlEndTagToken | XmlTextToken | XmlCdataToken;
XmlTokenizerLimitstypeStructural ceilings.interface XmlTokenizerLimits { /** Attributes on one element. The most any element in our corpus carries is 15. */ readonly maxAttributes: number; /** Tokens in one part. Our largest part yields 2303 elements. */ readonly maxTokens: number; /** * Length of the source, in UTF-16 code units. * * The only ceiling here denominated in the quantity that actually governs * allocation. Counting tokens, nodes or attributes-per-element each bounds * one dimension and misses the product: 20 000 elements carrying 256 * attributes apiece is 41.9 MiB of source, 20 001 nodes - half a percent of * `maxNodes` - and 817 MB of heap. Under a constrained heap that is a * `FATAL ERROR: Ineffective mark-compacts` process abort, which no * `try`/`catch` can see. * * The largest XML part in our 37-package corpus is 69 KB; the largest seen * anywhere in the wild is under a megabyte. 16 MiB is far past both. */ readonly maxSourceLength: number; }DEFAULT_TOKENIZER_LIMITSconstconst DEFAULT_TOKENIZER_LIMITS: XmlTokenizerLimits;
XmlTokenizerclassThe tokenizer.class XmlTokenizer { #private; readonly source: string; constructor(source: string, limits?: XmlTokenizerLimits); /** Index of the first token. Non-zero exactly when the source carries a BOM. */ get contentStart(): number; get position(): number; next(): XmlToken | undefined; /** Every remaining token, in order. */ [Symbol.iterator](): IterableIterator<XmlToken>; }tokenizefunctionEvery token insource, in order.function tokenize(source: string, limits?: XmlTokenizerLimits): XmlToken[];
SpanGaptypeA place where the token spans fail to tile the source.interface SpanGap { readonly kind: 'gap' | 'overlap' | 'prologue' | 'epilogue'; readonly at: number; readonly text: string; }checkSpanCoveragefunctionCheck that a token stream accounts for every character of its source.function checkSpanCoverage(source: string, tokens: readonly XmlToken[]): SpanGap[];
splitQNamefunctionSplit a qualified name. Exported because callers outside the tokenizer need it too.function splitQName(qname: string): { prefix: string; local: string; };
src/parse/xnode.ts
XElementtypeinterface XElement extends NodeBase { readonly type: 'element'; qname: string; prefix: string; local: string; attributes: XAttribute[]; children: XNode[]; /** * `<x/>` rather than `<x></x>`. * * Both are legal and both survive PowerPoint's reader, so the distinction is * ours to preserve: 33 elements in the corpus - all in `docProps` - use the * long form, and PowerPoint's own writer collapses them on resave. */ selfClosing: boolean; /** One past the `>` of the start tag. Children begin here. */ openTagEnd: number; /** The `<` of the end tag. Equal to {@link NodeBase.end} when self-closing. */ closeTagStart: number; /** One past the element name inside the start tag. */ nameEnd: number; /** * Where the whitespace before `>` or `/>` begins. * * Not cosmetic: 70 822 of the 98 777 self-closing tags in the corpus are * written `<a:off x="0" y="0" />`, so a serializer that assumes `/>` follows * the last attribute directly loses a character on most of the corpus. */ trailingSpaceStart: number; /** * True if this element carries any `xmlns` or `xmlns:*` attribute. * * Computed by the tokenizer, which has already read every attribute name. * {@link resolvePrefix} walks ancestors, and without this it also rescanned * each ancestor's whole attribute list: a 1.7 MB document 1000 deep with 200 * attributes per element took 37 seconds in `undeclaredPrefixes`. Both * numbers are legal - depth 1001 is under `maxDepth`, 200 attributes under * `maxAttributes` - and nothing counted the product. */ hasNamespaceDeclarations: boolean; }XTexttypeinterface XText extends NodeBase { readonly type: 'text'; /** References expanded, line endings normalized. Not the source text. */ value: string; /** * True if the run is `S` characters only - inter-element formatting. * * Derived from `value`, and therefore not `readonly`: sub-phase 0.6's * `setValue` maintains it. A derived field that an edit can leave stale is a * bug that shows up somewhere far away from the edit. */ whitespaceOnly: boolean; }XCDatatypeinterface XCData extends NodeBase { readonly type: 'cdata'; value: string; }XCommenttypeinterface XComment extends NodeBase { readonly type: 'comment'; value: string; }XProcessingInstructiontypeinterface XProcessingInstruction extends NodeBase { readonly type: 'processingInstruction'; target: string; data: string; }XDeclarationtypeinterface XDeclaration extends NodeBase { readonly type: 'declaration'; readonly version: string; readonly encoding: string | undefined; readonly standalone: string | undefined; }XNodetypetype XNode = XElement | XText | XCData | XComment | XProcessingInstruction | XDeclaration;
XDocumenttypeA parsed part.interface XDocument { readonly source: string; readonly encoding: XmlEncoding; readonly bom: boolean; readonly children: XNode[]; readonly root: XElement; readonly declaration: XDeclaration | undefined; }XmlParseLimitstypeCeilings on the tree, on top of the tokenizer's lexical ones.interface XmlParseLimits extends XmlTokenizerLimits { /** * Element nesting depth. * * The deepest part in our corpus reaches 23, inside a `p:timing` tree. * PowerPoint has no useful limit of its own - it opens a part nested 5000 * deep - so this number protects us rather than matching it. The builder uses * an explicit stack and never recurses, so the limit bounds what a hostile * document can allocate; it is not standing between us and a stack overflow. */ readonly maxDepth: number; /** Nodes in one document. */ readonly maxNodes: number; }DEFAULT_PARSE_LIMITSconstconst DEFAULT_PARSE_LIMITS: XmlParseLimits;
parseXmlStringfunctionBuild a tree from a decoded source string.function parseXmlString(source: string, limits?: XmlParseLimits): XDocument;
parseXmlfunctionDecode a part and build its tree. The entry point callers actually want.function parseXml(bytes: Uint8Array, limits?: XmlParseLimits): XDocument;
sourceOffunctionThe source a node came from.function sourceOf(document: XDocument, node: XNode): string;
startTagOffunctionThe start tag's source, which stays valid even when a child has been edited.function startTagOf(document: XDocument, element: XElement): string;
childElementsfunctionChild elements, in document order.function childElements(element: XElement): XElement[];
firstChildfunctionThe first child element with this qualified name.function firstChild(element: XElement, qname: string): XElement | undefined;
attributefunctionAn attribute by qualified name.function attribute(element: XElement, qname: string): XAttribute | undefined;
attributeValuefunctionAn attribute's normalized value.function attributeValue(element: XElement, qname: string): string | undefined;
descendantElementsfunctionEvery element in the subtree, in document order, starting withelement.function descendantElements(element: XElement): IterableIterator<XElement>;
textContentfunctionConcatenated text of the subtree, CDATA included, in document order.function textContent(element: XElement): string;
markDirtyfunctionMark a node as edited, and its ancestors with it.function markDirty(node: XNode, journal?: XNode[]): void;
markAttributeDirtyfunctionMark one attribute as edited.function markAttributeDirty(element: XElement, attribute: XAttribute, journal?: { nodes: XNode[]; attributes: XAttribute[]; }): void;checkDirtyInvariantfunctionPlaces wheredirtyhas been set without propagating, which the serializer would silently ignore.function checkDirtyInvariant(document: XDocument): CoverageGap[];
declaredNamespacesfunctionThexmlnsandxmlns:*bindings declared on this element.function declaredNamespaces(element: XElement): Map<string, string>;
resolvePrefixfunctionResolve a prefix to a namespace URI by walking up the tree.function resolvePrefix(element: XElement, prefix: string): string | undefined;
namespaceOffunctionThe namespace URI of an element, or''if it is in none.function namespaceOf(element: XElement): string | undefined;
attributeNamespaceOffunctionThe namespace URI of an attribute.function attributeNamespaceOf(owner: XElement, attr: XAttribute): string | undefined;
namespaceScopefunctionEvery prefix in scope at this element, the nearest binding winning.function namespaceScope(element: XElement): Map<string, string>;
prefixForfunctionA prefix already in scope here for this namespace, for writing a new name.function prefixFor(element: XElement, namespace: string): string | undefined;
prefixMapfunctionEvery prefix the document binds, with the set of URIs each is bound to.function prefixMap(document: XDocument): Map<string, Set<string>>;
undeclaredPrefixesfunctionAny prefix used in a name that nothing in scope binds.function undeclaredPrefixes(document: XDocument): { prefix: string; at: number; }[];xmlSpacefunctionThe effectivexml:spaceat an element, per XML 1.0 §2.10: the value is inherited by descendants until one overrides it.function xmlSpace(element: XElement): 'default' | 'preserve';
CoverageGaptypeA place where a tree's spans fail to account for the source exactly.interface CoverageGap { readonly kind: 'gap' | 'overlap' | 'malformed'; readonly at: number; readonly detail: string; }checkTreeCoveragefunctionCheck that the tree accounts for every character of the source.function checkTreeCoverage(document: XDocument): CoverageGap[];
src/emit/serialize.ts
serializeXmlStringfunctionSerialize a document.function serializeXmlString(document: XDocument): string;
serializeXmlfunctionSerialize a document to the bytes that go back into the package.function serializeXml(document: XDocument): Uint8Array;
serializeNodefunctionSerialize one node and its subtree.function serializeNode(document: XDocument, node: XNode): string;
src/emit/roundtrip.ts
RoundTripDifferencetypeOne way in which a document and its own serialization disagree.interface RoundTripDifference { /** * `parse` - the output is not well-formed, which is the worst case and the * only one that makes the rest of the comparison impossible. * `structure` - the trees differ at some node. * `prefix` - a prefix is bound to a different set of URIs. * `bom` - the byte order mark was gained or lost. */ readonly kind: 'parse' | 'structure' | 'prefix' | 'bom'; /** Offset in the **input** document, where one is attributable. */ readonly at: number; readonly detail: string; }checkRoundTripfunctionSerializedocument, parse the result, and report every way the two differ.function checkRoundTrip(document: XDocument, limits?: XmlParseLimits): RoundTripDifference[];
src/emit/canonical.ts
CanonicalXmlOptionstypeinterface CanonicalXmlOptions { /** * Rewrite an attribute value before it is written. * * The one caller is the round-trip comparator, which relabels relationship * ids: `r:embed="rId3"` and `r:embed="rId7"` are the same reference if the * two `.rels` agree about where rId3 and rId7 point. Passing the mapping in * rather than building it here keeps this module ignorant of relationships, * which are a package concept and not an XML one. * * `namespaceUri` is the attribute's namespace - the empty string for an * unprefixed attribute, which is in no namespace rather than in the element's * default one. */ readonly rewriteAttributeValue?: (value: string, namespaceUri: string, localName: string) => string; }canonicalXmlfunctionThe canonical form ofdocument.function canonicalXml(document: XDocument, options?: CanonicalXmlOptions): string;
firstDifferencefunctionThe first index at which two canonical forms differ, or -1.function firstDifference(a: string, b: string): number;
excerptfunctionA window oftextaroundat, for a message a person has to read.function excerpt(text: string, at: number, radius?: number): string;
src/edit/schema-order.gen.ts
SCHEMA_SOURCESconstconst SCHEMA_SOURCES: readonly { readonly file: string; readonly sha256: string; }[];
src/edit/schema-order.ts
SCHEMA_NAMESPACESconstThe four namespaces the ordering table was generated from.const SCHEMA_NAMESPACES: readonly string[];
isSchemaNamespacefunctionTrue when the ordering table was generated from this element's namespace.function isSchemaNamespace(namespace: string | undefined): boolean;
qualifiedKeyfunctionThe{namespace}localkey used throughout this module.function qualifiedKey(namespace: string, local: string): string;
elementKeyfunctionThe key of an element as it sits in its document.function elementKey(element: XElement): string | undefined;
childRanksfunctionThe ranks of every child name this parent admits, orundefined.function childRanks(parent: XElement): ReadonlyMap<string, number> | undefined;
childRankfunctionThe rank of one child name under this parent, orundefinedif it has none there.function childRank(parent: XElement, childKey: string): number | undefined;
insertionIndexfunctionThe index inparent.childrenat which a child of this name belongs.function insertionIndex(parent: XElement, childKey: string): number | undefined;
outOfOrderChildrenfunctionChildren that sit out of schema order, as adjacent pairs.function outOfOrderChildren(parent: XElement): { at: number; detail: string; }[];
src/mce/mce.ts
MC_ALTERNATE_CONTENTconstThe three MCE element names, unprefixed.const MC_ALTERNATE_CONTENT = "AlternateContent";
MC_CHOICEconstconst MC_CHOICE = "Choice";
MC_FALLBACKconstconst MC_FALLBACK = "Fallback";
isAlternateContentfunctionTrue if this element ismc:AlternateContent, whatever prefix it was written with.function isAlternateContent(element: XElement): boolean;
ignorableNamespacesfunctionNamespaces an ancestor-or-self declared ignorable, as URIs.function ignorableNamespaces(element: XElement): Set<string>;
mustUnderstandNamespacesfunctionNamespaces an ancestor-or-self declared must-understand, as URIs.function mustUnderstandNamespaces(element: XElement): Set<string>;
processContentNamesfunctionElements whose *content* survives when the element itself is ignored, as{namespace}localkeys.function processContentNames(element: XElement): Set<string>;
selectAlternateContentfunctionThe branch of anmc:AlternateContenta consumer supportingsupportedwould take, orundefinedif it would take none.function selectAlternateContent(alternateContent: XElement, supported: ReadonlySet<string>): XElement | undefined;
effectiveChildrenfunctionThe children a consumer supportingsupportedwould process, in order.function effectiveChildren(element: XElement, supported: ReadonlySet<string>): IterableIterator<XNode>;
effectiveAttributesfunctionThe attributes a consumer supportingsupportedwould read.function effectiveAttributes(element: XElement, supported: ReadonlySet<string>): IterableIterator<XAttribute>;
McProblemtypeOne way a document's markup-compatibility annotations do not hold together.interface McProblem { readonly at: number; readonly detail: string; }checkMarkupCompatibilityfunctionCheck every MCE construct in a subtree without throwing.function checkMarkupCompatibility(root: XElement, supported: ReadonlySet<string>): McProblem[];
boundNamespacesfunctionEvery namespace the document binds anywhere, as a set of URIs.function boundNamespaces(root: XElement): Set<string>;
src/edit/edit.ts
newAttributefunctionAn attribute that came from nowhere.function newAttribute(qname: string, value: string, quote?: '"' | "'"): XAttribute;
newElementfunctionAn element that came from nowhere. Born dirty, for the same reason.function newElement(qname: string, attributes?: readonly XAttribute[], children?: readonly XNode[]): XElement;
newTextfunctionA text node that came from nowhere.function newText(value: string): XText;
DirtyRestoretypeThe flags an edit set, so that its inverse can unset them.interface DirtyRestore { readonly nodes: readonly XNode[]; readonly attributes: readonly XAttribute[]; }SetAttributeEdittypeChange an attribute's value, or add it if the element does not have it.interface SetAttributeEdit extends EditBase { readonly kind: 'setAttribute'; readonly element: XElement; readonly qname: string; readonly value: string; }AddAttributeEdittypePut an attribute object at an exact position in an element's attribute list.interface AddAttributeEdit extends EditBase { readonly kind: 'addAttribute'; readonly element: XElement; readonly index: number; readonly attribute: XAttribute; }RemoveAttributeEdittypeinterface RemoveAttributeEdit extends EditBase { readonly kind: 'removeAttribute'; readonly element: XElement; readonly qname: string; }InsertChildEdittypePut a node at an exact index among a parent's children.interface InsertChildEdit extends EditBase { readonly kind: 'insertChild'; readonly parent: XElement; readonly index: number; readonly node: XNode; }RemoveChildEdittypeTake a node out of its parent.interface RemoveChildEdit extends EditBase { readonly kind: 'removeChild'; readonly parent: XElement; readonly node: XNode; }SetValueEdittypeReplace the value of a text node, a CDATA section or a comment.interface SetValueEdit extends EditBase { readonly kind: 'setValue'; readonly node: XText | XCData | XComment; readonly value: string; }XmlEdittypetype XmlEdit = SetAttributeEdit | AddAttributeEdit | RemoveAttributeEdit | InsertChildEdit | RemoveChildEdit | SetValueEdit;
applyEditfunctionPerform one edit and return the edit that undoes it.function applyEdit(edit: XmlEdit): XmlEdit;
applyEditsfunctionPerform a list of edits and return the list that undoes it, already reversed.function applyEdits(edits: readonly XmlEdit[]): XmlEdit[];
insertInOrderfunctionPlan the insertion of an OOXML element at the position the schema gives it.function insertInOrder(parent: XElement, child: XElement): InsertChildEdit;
src/mce/ext-lst.ts
extensionListfunctionTheextLstchild of an element, whichever namespace the schema puts it in.function extensionList(parent: XElement): XElement | undefined;
extensionsfunctionThe entries of an element'sextLst, in document order. Empty if it has none.function extensions(parent: XElement): XElement[];
findExtensionfunctionThe entry with thisuri, orundefined.function findExtension(parent: XElement, uri: string): XElement | undefined;
planAddExtensionfunctionAdd an extension entry, creating theextLstif the element has none.function planAddExtension(parent: XElement, uri: string): { edits: XmlEdit[]; ext: XElement; };planRemoveExtensionfunctionRemove an extension entry byuri.function planRemoveExtension(parent: XElement, uri: string): XmlEdit[];
Errors
Throws XmlError, whose code is one of:
- ERR_DOCTYPE_FORBIDDEN
- ERR_MALFORMED_XML
- ERR_MISMATCHED_TAG
- ERR_UNCLOSED_TAG
- ERR_ROOT_ELEMENT
- ERR_DUPLICATE_ATTRIBUTE
- ERR_UNKNOWN_ENTITY
- ERR_INVALID_CHARACTER
- ERR_UNSUPPORTED_ENCODING
- ERR_LIMIT_EXCEEDED
- ERR_INVALID_MCE
- ERR_SCHEMA_ORDER
- ERR_INVALID_EDIT
Try it live
Reading the deck…