ppt2json 0.3.2 → 0.3.4

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/dist/index.js CHANGED
@@ -1803,18 +1803,20 @@ async function getPicFill(type, node, warpObj) {
1803
1803
  }
1804
1804
  function getPicFillOpacity(node) {
1805
1805
  const aBlipNode = node["a:blip"];
1806
- const aphaModFixNode = getTextByPathList(aBlipNode, ["a:alphaModFix", "attrs"]);
1807
- let opacity = 1;
1808
- if (aphaModFixNode && aphaModFixNode["amt"] && aphaModFixNode["amt"] !== "") {
1809
- opacity = parseInt(aphaModFixNode["amt"]) / 1e5;
1810
- }
1811
- return opacity;
1806
+ return getBlipOpacityRatio(aBlipNode) ?? 1;
1812
1807
  }
1813
1808
  function getPicFilters(node) {
1814
1809
  if (!node) return null;
1815
1810
  const aBlipNode = node["a:blip"];
1816
1811
  if (!aBlipNode) return null;
1817
1812
  const filters = {};
1813
+ const opacity = getBlipOpacityRatio(aBlipNode);
1814
+ if (opacity !== void 0) {
1815
+ filters.opacity = formatFilterPercent(opacity);
1816
+ }
1817
+ if (aBlipNode["a:grayscl"]) {
1818
+ filters.grayscale = "100%";
1819
+ }
1818
1820
  const extLstNode = aBlipNode["a:extLst"];
1819
1821
  if (extLstNode && extLstNode["a:ext"]) {
1820
1822
  const extNodes = Array.isArray(extLstNode["a:ext"]) ? extLstNode["a:ext"] : [extLstNode["a:ext"]];
@@ -1828,7 +1830,12 @@ function getPicFilters(node) {
1828
1830
  if (effect["a14:saturation"]) {
1829
1831
  const satAttr = getTextByPathList(effect, ["a14:saturation", "attrs", "sat"]);
1830
1832
  if (satAttr) {
1831
- filters.saturation = parseInt(String(satAttr)) / 1e5;
1833
+ const saturation = parseInt(String(satAttr)) / 1e5;
1834
+ if (saturation <= 0) {
1835
+ filters.grayscale = "100%";
1836
+ } else {
1837
+ filters.saturation = saturation;
1838
+ }
1832
1839
  }
1833
1840
  }
1834
1841
  if (effect["a14:brightnessContrast"]) {
@@ -1866,16 +1873,34 @@ function getPicFilters(node) {
1866
1873
  async function getBgPicFill(bgPr, sorce, warpObj) {
1867
1874
  const picBase64 = await getPicFill(sorce, bgPr["a:blipFill"], warpObj);
1868
1875
  const aBlipNode = bgPr["a:blipFill"]["a:blip"];
1869
- const aphaModFixNode = getTextByPathList(aBlipNode, ["a:alphaModFix", "attrs"]);
1870
- let opacity = 1;
1871
- if (aphaModFixNode && aphaModFixNode["amt"] && aphaModFixNode["amt"] !== "") {
1872
- opacity = parseInt(aphaModFixNode["amt"]) / 1e5;
1873
- }
1876
+ const opacity = getBlipOpacityRatio(aBlipNode) ?? 1;
1874
1877
  return {
1875
1878
  picBase64,
1876
1879
  opacity
1877
1880
  };
1878
1881
  }
1882
+ function getBlipOpacityRatio(aBlipNode) {
1883
+ if (!aBlipNode) return void 0;
1884
+ const alphaNodes = Array.isArray(aBlipNode["a:alphaModFix"]) ? aBlipNode["a:alphaModFix"] : aBlipNode["a:alphaModFix"] ? [aBlipNode["a:alphaModFix"]] : [];
1885
+ if (!alphaNodes.length) return void 0;
1886
+ let opacity = 1;
1887
+ let hasOpacity = false;
1888
+ for (const alphaNode of alphaNodes) {
1889
+ const amt = alphaNode?.attrs?.amt;
1890
+ if (amt === void 0 || amt === "") continue;
1891
+ const parsed = parseInt(String(amt), 10);
1892
+ if (!Number.isFinite(parsed)) continue;
1893
+ opacity *= parsed / 1e5;
1894
+ hasOpacity = true;
1895
+ }
1896
+ return hasOpacity ? opacity : void 0;
1897
+ }
1898
+ function formatFilterPercent(value) {
1899
+ const percent = Math.max(0, Math.min(100, value * 100));
1900
+ const rounded = Math.round(percent * 1e3) / 1e3;
1901
+ const normalized = Number.isInteger(rounded) ? rounded.toFixed(0) : String(rounded);
1902
+ return `${normalized}%`;
1903
+ }
1879
1904
  function getGradientFill(node, warpObj) {
1880
1905
  const gsLst = node["a:gsLst"]["a:gs"];
1881
1906
  const colors = [];
@@ -3128,7 +3153,7 @@ function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNo
3128
3153
 
3129
3154
  // parser/shape.ts
3130
3155
  function shapeArc(cX, cY, rX, rY, stAng, endAng, isClose) {
3131
- let dData;
3156
+ let dData = "";
3132
3157
  let angle = stAng;
3133
3158
  if (endAng >= stAng) {
3134
3159
  while (angle <= endAng) {
@@ -3173,66 +3198,87 @@ function identifyShape(shapeData) {
3173
3198
  return matchShape(analysis);
3174
3199
  }
3175
3200
  function extractPathCommands(path) {
3176
- const commands = [];
3177
- const moveToList = normalizeToArray(path["a:moveTo"]);
3178
- moveToList.forEach((moveTo) => {
3179
- const pt = Array.isArray(moveTo?.["a:pt"]) ? moveTo["a:pt"][0] : moveTo?.["a:pt"];
3180
- if (pt) {
3181
- commands.push({
3182
- type: "moveTo",
3183
- points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3184
- });
3185
- }
3186
- });
3187
- const lineToList = normalizeToArray(path["a:lnTo"]);
3188
- lineToList.forEach((lnTo) => {
3189
- const pt = lnTo["a:pt"];
3190
- if (pt) {
3191
- commands.push({
3192
- type: "lineTo",
3193
- points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3194
- });
3195
- }
3196
- });
3197
- const cubicList = normalizeToArray(path["a:cubicBezTo"]);
3198
- cubicList.forEach((cubic) => {
3199
- const pts = normalizeToArray(cubic["a:pt"]);
3200
- const points = pts.map((pt) => ({
3201
- x: parseInt(pt.attrs?.x) || 0,
3202
- y: parseInt(pt.attrs?.y) || 0
3203
- }));
3204
- if (points.length === 3) {
3205
- commands.push({ type: "cubicBezTo", points });
3201
+ const orderedNodes = collectOrderedCommandNodes(path);
3202
+ return orderedNodes.flatMap(({ type, node }) => {
3203
+ switch (type) {
3204
+ case "moveTo": {
3205
+ const pt = Array.isArray(node?.["a:pt"]) ? node["a:pt"][0] : node?.["a:pt"];
3206
+ if (!pt) return [];
3207
+ return [{
3208
+ type: "moveTo",
3209
+ points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3210
+ }];
3211
+ }
3212
+ case "lineTo": {
3213
+ const pt = node?.["a:pt"];
3214
+ if (!pt) return [];
3215
+ return [{
3216
+ type: "lineTo",
3217
+ points: [{ x: parseInt(pt.attrs?.x) || 0, y: parseInt(pt.attrs?.y) || 0 }]
3218
+ }];
3219
+ }
3220
+ case "cubicBezTo": {
3221
+ const pts = normalizeToArray(node?.["a:pt"]);
3222
+ const points = pts.map((pt) => ({
3223
+ x: parseInt(pt.attrs?.x) || 0,
3224
+ y: parseInt(pt.attrs?.y) || 0
3225
+ }));
3226
+ return points.length === 3 ? [{ type: "cubicBezTo", points }] : [];
3227
+ }
3228
+ case "arcTo":
3229
+ return [{
3230
+ type: "arcTo",
3231
+ wR: parseInt(node?.attrs?.wR) || 0,
3232
+ hR: parseInt(node?.attrs?.hR) || 0,
3233
+ stAng: parseInt(node?.attrs?.stAng) || 0,
3234
+ swAng: parseInt(node?.attrs?.swAng) || 0
3235
+ }];
3236
+ case "quadBezTo": {
3237
+ const pts = normalizeToArray(node?.["a:pt"]);
3238
+ const points = pts.map((pt) => ({
3239
+ x: parseInt(pt.attrs?.x) || 0,
3240
+ y: parseInt(pt.attrs?.y) || 0
3241
+ }));
3242
+ return points.length ? [{ type: "quadBezTo", points }] : [];
3243
+ }
3244
+ case "close":
3245
+ return [{ type: "close" }];
3246
+ default:
3247
+ return [];
3206
3248
  }
3207
3249
  });
3208
- const arcList = normalizeToArray(path["a:arcTo"]);
3209
- arcList.forEach((arc) => {
3210
- commands.push({
3211
- type: "arcTo",
3212
- wR: parseInt(arc.attrs?.wR) || 0,
3213
- hR: parseInt(arc.attrs?.hR) || 0,
3214
- stAng: parseInt(arc.attrs?.stAng) || 0,
3215
- swAng: parseInt(arc.attrs?.swAng) || 0
3216
- });
3217
- });
3218
- const quadList = normalizeToArray(path["a:quadBezTo"]);
3219
- quadList.forEach((quad) => {
3220
- const pts = normalizeToArray(quad["a:pt"]);
3221
- const points = pts.map((pt) => ({
3222
- x: parseInt(pt.attrs?.x) || 0,
3223
- y: parseInt(pt.attrs?.y) || 0
3224
- }));
3225
- commands.push({ type: "quadBezTo", points });
3226
- });
3227
- if (path["a:close"]) {
3228
- commands.push({ type: "close" });
3229
- }
3230
- return commands;
3231
3250
  }
3232
3251
  function normalizeToArray(value) {
3233
3252
  if (!value) return [];
3234
3253
  return Array.isArray(value) ? value : [value];
3235
3254
  }
3255
+ function collectOrderedCommandNodes(path) {
3256
+ const commandNodes = [
3257
+ ...toOrderedCommandEntries(path["a:moveTo"], "moveTo"),
3258
+ ...toOrderedCommandEntries(path["a:lnTo"], "lineTo"),
3259
+ ...toOrderedCommandEntries(path["a:cubicBezTo"], "cubicBezTo"),
3260
+ ...toOrderedCommandEntries(path["a:arcTo"], "arcTo"),
3261
+ ...toOrderedCommandEntries(path["a:quadBezTo"], "quadBezTo"),
3262
+ ...toOrderedCommandEntries(path["a:close"], "close")
3263
+ ];
3264
+ return commandNodes.sort((left, right) => {
3265
+ if (left.order !== right.order) return left.order - right.order;
3266
+ return left.index - right.index;
3267
+ });
3268
+ }
3269
+ function toOrderedCommandEntries(value, type) {
3270
+ return normalizeToArray(value).map((node, index) => ({
3271
+ type,
3272
+ node,
3273
+ index,
3274
+ order: getNodeOrder(node)
3275
+ }));
3276
+ }
3277
+ function getNodeOrder(node) {
3278
+ const raw = node?.attrs?.order;
3279
+ const order = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
3280
+ return Number.isFinite(order) ? order : Number.MAX_SAFE_INTEGER;
3281
+ }
3236
3282
  function buildCustomShapeSegment(pathNode, w, h) {
3237
3283
  if (!pathNode || typeof pathNode !== "object") return "";
3238
3284
  const maxX = parseInt(pathNode.attrs?.w) || 0;
@@ -8206,9 +8252,28 @@ async function parse2(file) {
8206
8252
  }
8207
8253
  };
8208
8254
  }
8255
+ function getNodeByLocalName(node, localName) {
8256
+ if (!node || typeof node !== "object") return void 0;
8257
+ if (node[localName] !== void 0) return node[localName];
8258
+ for (const key of Object.keys(node)) {
8259
+ if (key === localName || key.endsWith(":" + localName)) {
8260
+ return node[key];
8261
+ }
8262
+ }
8263
+ return void 0;
8264
+ }
8265
+ function toNodeArray(node) {
8266
+ if (node === void 0 || node === null) return [];
8267
+ return Array.isArray(node) ? node : [node];
8268
+ }
8269
+ function getRelationshipArray(content) {
8270
+ const relationshipsNode = getNodeByLocalName(content, "Relationships");
8271
+ return toNodeArray(getNodeByLocalName(relationshipsNode, "Relationship"));
8272
+ }
8209
8273
  async function getContentTypes(zip) {
8210
8274
  const ContentTypesJson = await readXmlFile(zip, "[Content_Types].xml");
8211
- const subObj = ContentTypesJson["Types"]["Override"];
8275
+ const typesNode = getNodeByLocalName(ContentTypesJson, "Types");
8276
+ const subObj = toNodeArray(getNodeByLocalName(typesNode, "Override"));
8212
8277
  let slidesLocArray = [];
8213
8278
  let slideLayoutsLocArray = [];
8214
8279
  for (const item of subObj) {
@@ -8246,17 +8311,13 @@ async function getSlideInfo(zip) {
8246
8311
  }
8247
8312
  async function getTheme(zip) {
8248
8313
  const preResContent = await readXmlFile(zip, "ppt/_rels/presentation.xml.rels");
8249
- const relationshipArray = preResContent["Relationships"]["Relationship"];
8314
+ const relationshipArray = getRelationshipArray(preResContent);
8250
8315
  let themeURI;
8251
- if (relationshipArray.constructor === Array) {
8252
- for (const relationshipItem of relationshipArray) {
8253
- if (relationshipItem["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8254
- themeURI = relationshipItem["attrs"]["Target"];
8255
- break;
8256
- }
8316
+ for (const relationshipItem of relationshipArray) {
8317
+ if (relationshipItem["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8318
+ themeURI = relationshipItem["attrs"]["Target"];
8319
+ break;
8257
8320
  }
8258
- } else if (relationshipArray["attrs"]["Type"] === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") {
8259
- themeURI = relationshipArray["attrs"]["Target"];
8260
8321
  }
8261
8322
  const themeContent = await readXmlFile(zip, "ppt/" + themeURI);
8262
8323
  const themeColors = [];
@@ -8273,8 +8334,7 @@ async function getTheme(zip) {
8273
8334
  async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle) {
8274
8335
  const resName = sldFileName.replace("slides/slide", "slides/_rels/slide") + ".rels";
8275
8336
  const resContent = await readXmlFile(zip, resName);
8276
- let relationshipArray = resContent["Relationships"]["Relationship"];
8277
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8337
+ let relationshipArray = getRelationshipArray(resContent);
8278
8338
  let noteFilename = "";
8279
8339
  let layoutFilename = "";
8280
8340
  let masterFilename = "";
@@ -8339,8 +8399,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8339
8399
  const slideLayoutTables = await indexNodes(slideLayoutContent);
8340
8400
  const slideLayoutResFilename = layoutFilename.replace("slideLayouts/slideLayout", "slideLayouts/_rels/slideLayout") + ".rels";
8341
8401
  const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename);
8342
- relationshipArray = slideLayoutResContent["Relationships"]["Relationship"];
8343
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8402
+ relationshipArray = getRelationshipArray(slideLayoutResContent);
8344
8403
  for (const relationshipArrayItem of relationshipArray) {
8345
8404
  const relType = relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "");
8346
8405
  let relTarget = relationshipArrayItem["attrs"]["Target"];
@@ -8362,8 +8421,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8362
8421
  const slideMasterTables = indexNodes(slideMasterContent);
8363
8422
  const slideMasterResFilename = masterFilename.replace("slideMasters/slideMaster", "slideMasters/_rels/slideMaster") + ".rels";
8364
8423
  const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename);
8365
- relationshipArray = slideMasterResContent["Relationships"]["Relationship"];
8366
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8424
+ relationshipArray = getRelationshipArray(slideMasterResContent);
8367
8425
  for (const relationshipArrayItem of relationshipArray) {
8368
8426
  const relType = relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "");
8369
8427
  let relTarget = relationshipArrayItem["attrs"]["Target"];
@@ -8385,9 +8443,8 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8385
8443
  const themeResFileName = themeFilename.replace(themeName, "_rels/" + themeName) + ".rels";
8386
8444
  const themeResContent = await readXmlFile(zip, themeResFileName);
8387
8445
  if (themeResContent) {
8388
- relationshipArray = themeResContent["Relationships"]["Relationship"];
8446
+ relationshipArray = getRelationshipArray(themeResContent);
8389
8447
  if (relationshipArray) {
8390
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8391
8448
  for (const relationshipArrayItem of relationshipArray) {
8392
8449
  themeResObj[relationshipArrayItem["attrs"]["Id"]] = {
8393
8450
  "type": relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", ""),
@@ -8415,8 +8472,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
8415
8472
  }
8416
8473
  const digramResContent = await readXmlFile(zip, diagramResFileName);
8417
8474
  if (digramResContent) {
8418
- relationshipArray = digramResContent["Relationships"]["Relationship"];
8419
- if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray];
8475
+ relationshipArray = getRelationshipArray(digramResContent);
8420
8476
  for (const relationshipArrayItem of relationshipArray) {
8421
8477
  diagramResObj[relationshipArrayItem["attrs"]["Id"]] = {
8422
8478
  "type": relationshipArrayItem["attrs"]["Type"].replace("http://schemas.openxmlformats.org/officeDocument/2006/relationships/", ""),
@@ -9262,7 +9318,9 @@ function parseGradientPosition(value) {
9262
9318
  }
9263
9319
  function mapGradient(value) {
9264
9320
  if (!value || typeof value !== "object") return void 0;
9265
- const colors = Array.isArray(value.colors) ? value.colors.filter((stop) => stop && typeof stop === "object").map((stop) => ({
9321
+ const colors = Array.isArray(value.colors) ? value.colors.filter(
9322
+ (stop) => typeof stop === "object" && stop !== null
9323
+ ).map((stop) => ({
9266
9324
  pos: parseGradientPosition(stop.pos),
9267
9325
  color: mapFillColor(stop.color) ?? "#FFFFFF"
9268
9326
  })) : [];
@@ -9535,7 +9593,7 @@ function mapElement(raw) {
9535
9593
  fill,
9536
9594
  ...special ? { special: true } : {},
9537
9595
  text: hasText ? {
9538
- content: normalizedShapeText,
9596
+ content: normalizedShapeText ?? "",
9539
9597
  align: normalizeVAlign(raw.vAlign) ?? "middle",
9540
9598
  defaultColor: normalizeColor(raw.defaultColor) ?? normalizeColor(raw.color) ?? "#333",
9541
9599
  defaultFontName: raw.fontName ?? "",
@@ -9590,7 +9648,6 @@ function normalizeElement(element) {
9590
9648
  element.text.content = normalizeTextContent(element.text.content);
9591
9649
  }
9592
9650
  if (element.type === "image") {
9593
- delete element.filters;
9594
9651
  delete element.imageType;
9595
9652
  delete element.outline;
9596
9653
  }
@@ -9649,6 +9706,7 @@ function mapBaseElement(raw, exportedMeta) {
9649
9706
  rotate: raw.rotate,
9650
9707
  fill,
9651
9708
  opacity: raw.opacity,
9709
+ filters: raw.filters,
9652
9710
  outline: outline ?? void 0,
9653
9711
  shadow: shadow ?? void 0,
9654
9712
  flipH: raw.isFlipH,