Files
simstudioai--sim/apps/sim/lib/pptx-renderer/model/nodes/table-node.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

136 lines
3.6 KiB
TypeScript

/**
* Table node parser — handles graphicFrame elements containing a:tbl.
*/
import { emuToPx } from '../../parser/units'
import type { SafeXmlNode } from '../../parser/xml-parser'
import { type BaseNodeData, parseBaseProps } from './base-node'
import { parseTextBody, type TextBody } from './shape-node'
export interface TableCell {
gridSpan: number
rowSpan: number
hMerge: boolean
vMerge: boolean
textBody?: TextBody
/** @internal Raw XML node — opaque to consumers. Use serializePresentation() for JSON-safe data. */
properties?: SafeXmlNode
}
interface TableRow {
height: number
cells: TableCell[]
}
export interface TableNodeData extends BaseNodeData {
nodeType: 'table'
columns: number[]
rows: TableRow[]
/** @internal Raw XML node — opaque to consumers. Use serializePresentation() for JSON-safe data. */
properties?: SafeXmlNode
tableStyleId?: string
}
/**
* Parse a single table cell (`a:tc`).
*/
function parseCell(tcNode: SafeXmlNode): TableCell {
const gridSpan = tcNode.numAttr('gridSpan') ?? 1
const rowSpan = tcNode.numAttr('rowSpan') ?? 1
const hMerge = tcNode.attr('hMerge') === '1' || tcNode.attr('hMerge') === 'true'
const vMerge = tcNode.attr('vMerge') === '1' || tcNode.attr('vMerge') === 'true'
// Cell text body
const txBody = tcNode.child('txBody')
const textBody = parseTextBody(txBody)
// Cell properties
const tcPr = tcNode.child('tcPr')
return {
gridSpan,
rowSpan,
hMerge,
vMerge,
textBody,
properties: tcPr.exists() ? tcPr : undefined,
}
}
/**
* Parse a table row (`a:tr`).
*/
function parseRow(trNode: SafeXmlNode): TableRow {
const height = emuToPx(trNode.numAttr('h') ?? 0)
const cells: TableCell[] = []
for (const tcNode of trNode.children('tc')) {
cells.push(parseCell(tcNode))
}
return { height, cells }
}
/**
* Locate the `a:tbl` element inside a graphicFrame.
* Path: `a:graphic > a:graphicData > a:tbl`
*/
function findTable(frameNode: SafeXmlNode): SafeXmlNode {
const graphic = frameNode.child('graphic')
const graphicData = graphic.child('graphicData')
return graphicData.child('tbl')
}
/**
* Extract the table style ID from tblPr.
* It can be in `a:tblStyle@val` or as a direct `tblStyle` attribute.
*/
function extractTableStyleId(tblPr: SafeXmlNode): string | undefined {
// Try <a:tableStyleId>{UUID}</a:tableStyleId> (most common in OOXML)
const tableStyleIdNode = tblPr.child('tableStyleId')
if (tableStyleIdNode.exists()) {
return tableStyleIdNode.text() || tableStyleIdNode.attr('val') || undefined
}
// Try <a:tblStyle val="{UUID}"/>
const tblStyleNode = tblPr.child('tblStyle')
if (tblStyleNode.exists()) {
return tblStyleNode.attr('val') ?? (tblStyleNode.text() || undefined)
}
// Try direct attribute
return tblPr.attr('tblStyle') ?? undefined
}
/**
* Parse a graphicFrame XML node containing a table into TableNodeData.
*/
export function parseTableNode(frameNode: SafeXmlNode): TableNodeData {
const base = parseBaseProps(frameNode)
const tbl = findTable(frameNode)
// --- Column widths ---
const tblGrid = tbl.child('tblGrid')
const columns: number[] = []
for (const gridCol of tblGrid.children('gridCol')) {
columns.push(emuToPx(gridCol.numAttr('w') ?? 0))
}
// --- Rows ---
const rows: TableRow[] = []
for (const trNode of tbl.children('tr')) {
rows.push(parseRow(trNode))
}
// --- Table properties ---
const tblPr = tbl.child('tblPr')
const tableStyleId = extractTableStyleId(tblPr)
return {
...base,
nodeType: 'table',
columns,
rows,
properties: tblPr.exists() ? tblPr : undefined,
tableStyleId,
}
}