feat: improve report generation workflow
This commit is contained in:
@@ -9,7 +9,6 @@ import type { Repository } from 'typeorm'
|
||||
|
||||
import { TemplateEntitySchema, type TemplateEntity } from '../entities/TemplateEntity'
|
||||
import type {
|
||||
TemplateFieldMapping,
|
||||
TemplateInput,
|
||||
TemplateItem,
|
||||
TemplatePlaceholder,
|
||||
@@ -33,8 +32,15 @@ const BUILT_IN_PPT_PLACEHOLDERS = new Set([
|
||||
'hobby',
|
||||
'game',
|
||||
'food',
|
||||
'class_image'
|
||||
'class_image',
|
||||
'meImage',
|
||||
'me_image',
|
||||
'workImage1',
|
||||
'work_image_1',
|
||||
'workImage2',
|
||||
'work_image_2'
|
||||
])
|
||||
const BUILT_IN_PPT_IMAGE_PLACEHOLDERS = ['class_image', 'meImage', 'workImage1', 'workImage2']
|
||||
|
||||
function getTemplateStoragePath(): string {
|
||||
return join(app.getPath('userData'), TEMPLATE_FOLDER_NAME)
|
||||
@@ -82,23 +88,6 @@ function parseStoredPlaceholders(value: string): TemplatePlaceholder[] {
|
||||
}
|
||||
}
|
||||
|
||||
function parseStoredFieldMappings(value: string): TemplateFieldMapping {
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
||||
? Object.fromEntries(
|
||||
Object.entries(parsed).filter(
|
||||
(entry): entry is [string, string] =>
|
||||
typeof entry[0] === 'string' && typeof entry[1] === 'string'
|
||||
)
|
||||
)
|
||||
: {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function mapTemplateEntity(entity: TemplateEntity): TemplateItem {
|
||||
return {
|
||||
id: entity.id,
|
||||
@@ -110,7 +99,6 @@ function mapTemplateEntity(entity: TemplateEntity): TemplateItem {
|
||||
filePath: resolveTemplateFilePath(entity.filePath),
|
||||
fileExtension: entity.fileExtension,
|
||||
placeholders: parseStoredPlaceholders(entity.placeholders),
|
||||
fieldMappings: parseStoredFieldMappings(entity.fieldMappings),
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt
|
||||
}
|
||||
@@ -161,6 +149,25 @@ function isPictureShape(slideXml: string, shapeNameIndex: number): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function getXmlAttributeValue(xml: string, attributeName: string): string | null {
|
||||
const match = xml.match(new RegExp(`${attributeName}="([^"]*)"`))
|
||||
return match ? decodeXml(match[1]) : null
|
||||
}
|
||||
|
||||
function normalizePlaceholderName(name: string): string {
|
||||
return decodeXml(name).trim().replace(/^field[:_-]/i, '')
|
||||
}
|
||||
|
||||
function getKnownPlaceholderName(name: string): string | null {
|
||||
const placeholderName = normalizePlaceholderName(name)
|
||||
|
||||
if (BUILT_IN_PPT_PLACEHOLDERS.has(name) || BUILT_IN_PPT_PLACEHOLDERS.has(placeholderName)) {
|
||||
return placeholderName
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function upsertPlaceholder(
|
||||
placeholdersByName: Map<string, TemplatePlaceholder>,
|
||||
name: string,
|
||||
@@ -195,17 +202,24 @@ async function parsePptxPlaceholders(filePath: string): Promise<TemplatePlacehol
|
||||
|
||||
const slideXml = await slideFile.async('text')
|
||||
|
||||
for (const shapeMatch of slideXml.matchAll(/<p:cNvPr[^>]*name="([^"]+)"/g)) {
|
||||
const rawName = shapeMatch[1]
|
||||
const name = decodeXml(rawName)
|
||||
for (const shapeMatch of slideXml.matchAll(/<p:cNvPr[^>]*>/g)) {
|
||||
const shapeXml = shapeMatch[0]
|
||||
const candidateNames = [
|
||||
getXmlAttributeValue(shapeXml, 'name'),
|
||||
getXmlAttributeValue(shapeXml, 'descr'),
|
||||
getXmlAttributeValue(shapeXml, 'title')
|
||||
].filter((value): value is string => Boolean(value))
|
||||
const placeholderName = candidateNames
|
||||
.map(getKnownPlaceholderName)
|
||||
.find((value): value is string => Boolean(value))
|
||||
|
||||
if (!BUILT_IN_PPT_PLACEHOLDERS.has(name) && !/^field[:_-]/i.test(name)) {
|
||||
if (!placeholderName) {
|
||||
continue
|
||||
}
|
||||
|
||||
upsertPlaceholder(
|
||||
placeholdersByName,
|
||||
name.replace(/^field[:_-]/i, ''),
|
||||
placeholderName,
|
||||
isPictureShape(slideXml, shapeMatch.index ?? 0) ? 'image' : 'text'
|
||||
)
|
||||
}
|
||||
@@ -215,6 +229,10 @@ async function parsePptxPlaceholders(filePath: string): Promise<TemplatePlacehol
|
||||
}
|
||||
}
|
||||
|
||||
for (const placeholderName of BUILT_IN_PPT_IMAGE_PLACEHOLDERS) {
|
||||
upsertPlaceholder(placeholdersByName, placeholderName, 'image')
|
||||
}
|
||||
|
||||
return [...placeholdersByName.values()].sort((first, second) =>
|
||||
first.name.localeCompare(second.name, 'zh-CN')
|
||||
)
|
||||
@@ -309,16 +327,6 @@ export async function saveTemplate(input: TemplateInput): Promise<TemplateItem>
|
||||
: existingTemplate
|
||||
? parseStoredPlaceholders(existingTemplate.placeholders)
|
||||
: []
|
||||
const placeholderNames = new Set(placeholders.map((placeholder) => placeholder.name))
|
||||
const previousMappings = existingTemplate
|
||||
? parseStoredFieldMappings(existingTemplate.fieldMappings)
|
||||
: {}
|
||||
const inputMappings = input.fieldMappings ?? previousMappings
|
||||
const fieldMappings = Object.fromEntries(
|
||||
Object.entries(inputMappings)
|
||||
.map(([placeholder, field]) => [placeholder.trim(), field.trim()])
|
||||
.filter(([placeholder, field]) => placeholderNames.has(placeholder) && field.length > 0)
|
||||
)
|
||||
|
||||
const entity: TemplateEntity = {
|
||||
id,
|
||||
@@ -330,7 +338,7 @@ export async function saveTemplate(input: TemplateInput): Promise<TemplateItem>
|
||||
filePath: nextStoredFilePath,
|
||||
fileExtension: extension,
|
||||
placeholders: JSON.stringify(placeholders),
|
||||
fieldMappings: JSON.stringify(fieldMappings),
|
||||
fieldMappings: '{}',
|
||||
createdAt: existingTemplate?.createdAt ?? now,
|
||||
updatedAt: now
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user