Skip to content

Commit

Permalink
feat: updatae
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Dec 5, 2024
1 parent 392bc7b commit 4977255
Show file tree
Hide file tree
Showing 44 changed files with 384 additions and 209 deletions.
111 changes: 88 additions & 23 deletions src/core/OXML.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { deepMerge, getObjectValueByPath, setObjectValueByPath } from './utils'

export type OXMLProto = new (...args: any[]) => OXML

export interface OXMLAttributeDefinition {
type: string
name: string
alias: string
type: string | Record<string, any>
defaultValue?: any
}

export interface OXMLPropertyDefinition {
type: string
name: string
alias: string
defaultValue?: any
}

Expand All @@ -19,8 +24,8 @@ export interface OXMLChildDefinition {
export interface OXMLDefinition {
tag?: string
namespace?: string
attributes?: Map<string, OXMLAttributeDefinition>
properties?: Map<string, OXMLPropertyDefinition>
attributes?: Record<string, OXMLAttributeDefinition>
properties?: Record<string, OXMLPropertyDefinition>
children?: OXMLChildDefinition[]
}

Expand Down Expand Up @@ -55,8 +60,8 @@ export function defineAttribute(
definition = {} as OXMLDefinition
OXML.protoToDefinition.set(proto, definition)
}
definition.attributes ??= new Map()
definition.attributes.set(attrName, { type, defaultValue })
definition.attributes ??= {}
definition.attributes[attrName] = { name, alias: attrName, type, defaultValue }
Object.defineProperty(proto, name, {
get() {
return (this as OXML).getAttribute(attrName)
Expand All @@ -74,11 +79,11 @@ export function defineProperty(propName: string) {
definition = {} as OXMLDefinition
OXML.protoToDefinition.set(proto, definition)
}
definition.properties ??= new Map()
definition.properties.set(propName, {})
definition.properties ??= {}
definition.properties[propName] = { name, alias: propName }
Object.defineProperty(proto, name, {
get() {
return (this as OXML).getAttribute(propName)
return (this as OXML).offsetGet(propName)
},
})
}
Expand Down Expand Up @@ -121,16 +126,41 @@ export class OXML {
return this.tagToConstructor.get(tag)
}

static getDefinition(tag: string): OXMLDefinition | undefined {
const proto = this.getConstructor(tag)?.prototype
return proto ? this.protoToDefinition.get(proto) : undefined
static getDefinition(proto: any): OXMLDefinition | undefined {
let definition: OXMLDefinition | undefined
let cur = proto
while (cur) {
const _definition = this.protoToDefinition.get(cur)
if (_definition) {
definition = deepMerge(definition ?? {}, _definition)
}
cur = Object.getPrototypeOf(cur)
}
return definition
}

static make<T extends OXML = OXML>(source: string | Element): T {
let tag: string
let element: Element | undefined
if (typeof source === 'string') {
tag = source
}
else {
tag = source.tagName
element = source
}
const oxml = new (this.getConstructor(tag) ?? OXML)()
if (element) {
oxml.fromElement(element)
}
return oxml as T
}

declare tag?: string
declare element: Element

definition(): OXMLDefinition | undefined {
return OXML.protoToDefinition.get(this.constructor.prototype as any)
return OXML.getDefinition(this)
}

getSetterValue(type: string, value: any): any {
Expand Down Expand Up @@ -192,7 +222,7 @@ export class OXML {
}

setAttribute(name: string, value: any): void {
const definition = this.definition()?.attributes?.get(name)
const definition = this.definition()?.attributes?.[name]
let newValue = value
if (definition) {
try {
Expand All @@ -207,7 +237,7 @@ export class OXML {

getAttribute(name: string): any | undefined {
const value = this.element.getAttribute(name)
const definition = this.definition()?.attributes?.get(name)
const definition = this.definition()?.attributes?.[name]
if (value === undefined) {
return value ?? definition?.defaultValue
}
Expand All @@ -230,12 +260,20 @@ export class OXML {
)
}

offsetSet(path: string, value: any): void {
return setObjectValueByPath(this, path, value)
}

offsetGet(path: string): any | undefined {
return getObjectValueByPath(this, path)
}

getChild(tag: string): OXML | undefined {
const element = Array.from(this.element.children).find((element) => {
return element.tagName === tag || element.localName === tag
})
if (element) {
return new (OXML.getConstructor(element.tagName) ?? OXML)().fromElement(element)
return OXML.make(element)
}
return undefined
}
Expand All @@ -244,7 +282,7 @@ export class OXML {
return Array.from(this.element.children)
.map((element) => {
if (!tag || (element.tagName === tag || element.localName === tag)) {
return new (OXML.getConstructor(element.tagName) ?? OXML)().fromElement(element)
return OXML.make(element)
}
return undefined
})
Expand Down Expand Up @@ -276,13 +314,40 @@ export class OXML {
}

toJSON(): Record<string, any> {
const definition = this.definition()
const properties: Record<string, any> = {}
if (definition?.properties) {
Object.values(definition.properties).forEach((property) => {
let value = this.offsetGet(property.alias)
if (value instanceof OXML) {
value = value.toJSON()
}
else if (Array.isArray(value)) {
value = value.map((v) => {
if (v instanceof OXML) {
return v.toJSON()
}
return v
})
}
if (value !== undefined) {
properties[property.name] = value
}
})
}
definition?.children?.forEach((child) => {
child.tag
})
return {
...this.getAttributes(),
...Object.fromEntries(this.getChildren().map((child) => {
const tag = child.tag ?? child.element.tagName
const tagArr = tag.split(':')
return [tagArr[tagArr.length - 1], child.toJSON()]
})),
...properties,
}
// return {
// ...this.getAttributes(),
// ...Object.fromEntries(this.getChildren().map((child) => {
// const tag = child.tag ?? child.element.tagName
// const tagArr = tag.split(':')
// return [tagArr[tagArr.length - 1], child.toJSON()]
// })),
// }
}
}
70 changes: 70 additions & 0 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export function getNestedValue(obj: any, path: (string | number)[], fallback?: any): any {
const last = path.length - 1
if (last < 0)
return obj === undefined ? fallback : obj
for (let i = 0; i < last; i++) {
if (obj == null) {
return fallback
}
obj = obj[path[i]]
}
if (obj == null)
return fallback
return obj[path[last]] === undefined ? fallback : obj[path[last]]
}

export function getObjectValueByPath(obj: any, path: string, fallback?: any): any {
// credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
if (obj == null || !path || typeof path !== 'string')
return fallback
if (obj[path] !== undefined)
return obj[path]
path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
path = path.replace(/^\./, '') // strip a leading dot
return getNestedValue(obj, path.split('.'), fallback)
}

export function setNestedValue(obj: any, path: (string | number)[], value: any): void {
const last = path.length - 1
for (let i = 0; i < last; i++) {
if (typeof obj[path[i]] !== 'object')
obj[path[i]] = {}
obj = obj[path[i]]
}
obj[path[last]] = value
}

export function setObjectValueByPath(obj: any, path: string, value: any): void {
if (typeof obj !== 'object' || !path)
return
path = path.replace(/\[(\w+)\]/g, '.$1')
path = path.replace(/^\./, '')
return setNestedValue(obj, path.split('.'), value)
}

export function isObject(objectable: any): objectable is object {
return objectable !== null && typeof objectable === 'object' && !Array.isArray(objectable)
}

export function deepMerge(
source: Record<string, any> = {},
target: Record<string, any> = {},
out: Record<string, any> = {},
): Record<string, any> {
for (const key in source) {
out[key] = source[key]
}
for (const key in target) {
const sourceProperty = source[key]
const targetProperty = target[key]
if (
isObject(sourceProperty)
&& isObject(targetProperty)
) {
out[key] = deepMerge(sourceProperty, targetProperty)
continue
}
out[key] = targetProperty
}
return out
}
4 changes: 2 additions & 2 deletions src/openxml/drawing/BlipFill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { defineAttribute, defineChild, defineElement, OXML } from '../../core'
*/
@defineElement('a:blipFill')
export class BlipFill extends OXML {
@defineAttribute('rotWithShape', 'boolean') rotWithShape?: boolean
@defineAttribute('dpi', 'number') dpi?: number
@defineAttribute('rotWithShape', 'boolean') declare rotWithShape?: boolean
@defineAttribute('dpi', 'number') declare dpi?: number

@defineChild('a:blip') declare blip: Blip
@defineChild('a:srcRect') declare srcRect: SourceRectangle
Expand Down
20 changes: 10 additions & 10 deletions src/openxml/drawing/BodyProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { TextAnchoringTypeValues } from './TextAnchoringTypeValues'
*/
@defineElement('a:bodyPr')
export class BodyProperties extends OXML {
@defineAttribute('anchor', TextAnchoringTypeValues) anchor?: TextAnchoringTypeValues
@defineAttribute('anchorCtr', 'boolean') anchorCtr?: boolean
@defineAttribute('spcFirstLastPara', 'boolean') spcFirstLastPara?: boolean
@defineAttribute('lIns', 'emu') lIns?: number
@defineAttribute('tIns', 'emu') tIns?: number
@defineAttribute('rIns', 'emu') rIns?: number
@defineAttribute('bIns', 'emu') bIns?: number
@defineAttribute('rot', 'degree') rot?: number
@defineAttribute('wrap') wrap?: TextWrappingValues
@defineAttribute('upright', 'boolean') upright?: boolean
@defineAttribute('anchor', TextAnchoringTypeValues) declare anchor?: TextAnchoringTypeValues
@defineAttribute('anchorCtr', 'boolean') declare anchorCtr?: boolean
@defineAttribute('spcFirstLastPara', 'boolean') declare spcFirstLastPara?: boolean
@defineAttribute('lIns', 'emu') declare lIns?: number
@defineAttribute('tIns', 'emu') declare tIns?: number
@defineAttribute('rIns', 'emu') declare rIns?: number
@defineAttribute('bIns', 'emu') declare bIns?: number
@defineAttribute('rot', 'degree') declare rot?: number
@defineAttribute('wrap') declare wrap?: TextWrappingValues
@defineAttribute('upright', 'boolean') declare upright?: boolean
}
12 changes: 6 additions & 6 deletions src/openxml/drawing/CustomGeometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { defineChild, defineElement, OXML } from '../../core'
*/
@defineElement('a:custGeom')
export class CustomGeometry extends OXML {
@defineChild('a:ahLst') ahLst?: AdjustHandleList
@defineChild('a:avLst') avLst?: AdjustValueList
@defineChild('a:cxnLst') cxnLst?: ConnectionSiteList
@defineChild('a:gdLst') gdLst?: ShapeGuideList
@defineChild('a:pathLst') pathLst?: PathList
@defineChild('a:rect') rect?: Rectangle
@defineChild('a:ahLst') declare ahLst?: AdjustHandleList
@defineChild('a:avLst') declare avLst?: AdjustValueList
@defineChild('a:cxnLst') declare cxnLst?: ConnectionSiteList
@defineChild('a:gdLst') declare gdLst?: ShapeGuideList
@defineChild('a:pathLst') declare pathLst?: PathList
@defineChild('a:rect') declare rect?: Rectangle
}
16 changes: 8 additions & 8 deletions src/openxml/drawing/EffectList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { defineChild, defineElement, OXML } from '../../core'
@defineElement('a:effectLst')
export class EffectList extends OXML {
// TODO
@defineChild('blur') blur?: OXML
@defineChild('fillOverlay') fillOverlay?: OXML
@defineChild('glow') glow?: OXML
@defineChild('innerShdw') innerShdw?: OXML
@defineChild('outerShdw') outerShdw?: OXML
@defineChild('prstShdw') prstShdw?: OXML
@defineChild('reflection') reflection?: OXML
@defineChild('softEdge') softEdge?: OXML
@defineChild('blur') declare blur?: OXML
@defineChild('fillOverlay') declare fillOverlay?: OXML
@defineChild('glow') declare glow?: OXML
@defineChild('innerShdw') declare innerShdw?: OXML
@defineChild('outerShdw') declare outerShdw?: OXML
@defineChild('prstShdw') declare prstShdw?: OXML
@defineChild('reflection') declare reflection?: OXML
@defineChild('softEdge') declare softEdge?: OXML
}
2 changes: 1 addition & 1 deletion src/openxml/drawing/HslColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export class HslColor extends OXML {
@defineAttribute('sat', 'percentage') declare sat: number
@defineAttribute('lum', 'percentage') declare lum: number

@defineChild('a:alpha') alpha?: Alpha
@defineChild('a:alpha') declare alpha?: Alpha
}
23 changes: 22 additions & 1 deletion src/openxml/drawing/Outline.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { defineElement, OXML } from '../../core'
import { defineChild, defineElement, OXML } from '../../core'

/**
* https://learn.microsoft.com/dotnet/api/documentformat.openxml.drawing.outline
*/
@defineElement('a:ln')
export class Outline extends OXML {
@defineChild('a:bevel') declare bevel: OXML
@defineChild('a:custDash') declare custDash: OXML
@defineChild('a:extLst') declare extLst: OXML
@defineChild('a:gradFill') declare gradFill: OXML
@defineChild('a:headEnd') declare headEnd: OXML
@defineChild('a:miter') declare miter: OXML
@defineChild('a:noFill') declare noFill: OXML
@defineChild('a:pattFill') declare pattFill: OXML
@defineChild('a:prstDash') declare prstDash: OXML
@defineChild('a:round') declare round: OXML
@defineChild('a:solidFill') declare solidFill: OXML
@defineChild('a:tailEnd') declare tailEnd: OXML

// <a:solidFill>
// <a:srgbClr val="transparent">
// <a:alpha val="100000"/>
// </a:srgbClr>
// </a:solidFill>
// <a:prstDash val="solid" />
// <a:headEnd type="none" />
// <a:tailEnd type="none" />
//
}
4 changes: 2 additions & 2 deletions src/openxml/drawing/Paragraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class Paragraph extends OXML {
@defineProperty('pPr.marL', 0) declare marginLeft: number
@defineProperty('pPr.marR', 0) declare marginRight: number
@defineProperty('pPr.indent', 0) declare textIndent: number
@defineProperty('pPr.lvl') level?: number
@defineProperty('pPr.fontAlgn') fontAlign?: string
@defineProperty('pPr.lvl') declare level?: number
@defineProperty('pPr.fontAlgn') declare fontAlign?: string

get textAlign(): TextAlignmentTypeValues | undefined { return this.pPr.algn }
get rightToLeft(): string | undefined { return this.pPr.rtl }
Expand Down
Loading

0 comments on commit 4977255

Please sign in to comment.