From 8b434a9c37dc252091814c40de11dade9b7a194a Mon Sep 17 00:00:00 2001 From: nodaguti Date: Thu, 19 Sep 2024 14:27:44 +0900 Subject: [PATCH] Replace xml-js with fast-xml-parser --- package-lock.json | 46 ++++++++++++------- packages/vast-parser/package.json | 2 +- packages/vast-parser/src/VASTParser.ts | 44 ++++++++++++------ packages/vast-parser/src/XmlVAST.ts | 58 ++++++++++++------------ packages/vast-parser/tests/index.test.ts | 5 +- packages/vmap-parser/package.json | 4 +- packages/vmap-parser/src/VMAPParser.ts | 17 +++++-- packages/vmap-parser/src/XmlVMAP.ts | 8 +++- 8 files changed, 115 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd61e54..cba7691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1421,6 +1421,28 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "dev": true, @@ -3139,6 +3161,12 @@ "node": ">=6" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "dev": true, @@ -3923,20 +3951,6 @@ } } }, - "node_modules/xml-js": { - "version": "1.6.11", - "license": "MIT", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/xml-js/node_modules/sax": { - "version": "1.3.0", - "license": "ISC" - }, "node_modules/yallist": { "version": "4.0.0", "dev": true, @@ -4280,7 +4294,7 @@ "version": "0.0.3", "license": "MIT", "dependencies": { - "xml-js": "^1.6.11" + "fast-xml-parser": "^4.5.0" }, "devDependencies": { "buffer": "^6.0.3", @@ -4310,7 +4324,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "xml-js": "^1.6.11" + "fast-xml-parser": "^4.5.0" }, "devDependencies": { "buffer": "^6.0.3", diff --git a/packages/vast-parser/package.json b/packages/vast-parser/package.json index 45b535d..ed526a1 100644 --- a/packages/vast-parser/package.json +++ b/packages/vast-parser/package.json @@ -20,7 +20,7 @@ "test": "uvu -r ts-node/register tests" }, "dependencies": { - "xml-js": "^1.6.11" + "fast-xml-parser": "^4.5.0" }, "devDependencies": { "buffer": "^6.0.3", diff --git a/packages/vast-parser/src/VASTParser.ts b/packages/vast-parser/src/VASTParser.ts index 68bf34c..7dccfdc 100644 --- a/packages/vast-parser/src/VASTParser.ts +++ b/packages/vast-parser/src/VASTParser.ts @@ -1,4 +1,4 @@ -import convert from 'xml-js'; +import { XMLParser, XMLBuilder } from 'fast-xml-parser'; import { mapArrayOrElem } from '../../utils/src'; import type { XmlVASTRoot } from "./XmlVAST"; import Creative from "./Creative"; @@ -27,6 +27,20 @@ export default class VASTParser { */ constructor(vast: string) { this._originalVAST = vast; + this._parser = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: '', + attributesGroupName: '_attributes', + cdataPropName: '_cdata', + alwaysCreateTextNode: true, + textNodeName: '_text', + }); + this._builder = new XMLBuilder({ + attributeNamePrefix: '', + attributesGroupName: '_attributes', + cdataPropName: '_cdata', + textNodeName: '_text', + }); } /** @@ -38,21 +52,23 @@ export default class VASTParser { } private _originalVAST: string; + private _parser: XMLParser; + private _builder: XMLBuilder; private _parseVAST(vast: string): VAST | null { - const vastRoot: XmlVASTRoot = convert.xml2js(vast, { compact: true }) as XmlVASTRoot; + const vastRoot: XmlVASTRoot = this._parser.parse(vast) as XmlVASTRoot; const vastObj = vastRoot.VAST; const ads = mapArrayOrElem(vastObj.Ad, ad => { if (!ad) return null; const inLineNode = ad.InLine; if (!inLineNode) return null; - + const errorNode = inLineNode.Error; let errors: string[] = []; if (errorNode) { errors = mapArrayOrElem(errorNode, error => { - if (!error._cdata) return null; - return error._cdata; + if (!error._cdata || !error._cdata._text) return null; + return error._cdata._text; }); } @@ -60,11 +76,11 @@ export default class VASTParser { let extensions: Extension[] = []; if (extensionsNode && extensionsNode.Extension) { extensions = mapArrayOrElem(extensionsNode.Extension, extension => { - const customXML = convert.js2xml(extension as convert.ElementCompact, { compact: true }); + const customXML = this._builder.build(extension); return new Extension(customXML, extension._attributes?.type); }); } - + const creativeNode = inLineNode.Creatives?.Creative; if (!creativeNode) return null; const creatives = mapArrayOrElem(creativeNode, creative => { @@ -86,7 +102,7 @@ export default class VASTParser { const mediaFileNode = linearNode?.MediaFiles?.MediaFile; if (!mediaFileNode) return null; const mediaFiles = mapArrayOrElem(mediaFileNode, mediaFile => { - const url = mediaFile._cdata; + const url = mediaFile._cdata._text; const delivery = mediaFile._attributes.delivery; const type = mediaFile._attributes.type; const width = parseInt(mediaFile._attributes.width); @@ -110,35 +126,35 @@ export default class VASTParser { ); return newMediaFile; }); - + const trackingNode = linearNode?.TrackingEvents?.Tracking; if (trackingNode) { trackingEvents = mapArrayOrElem(trackingNode, tracking => { const event = tracking._attributes.event; - const url = tracking._cdata; + const url = tracking._cdata._text; return new Tracking(url, event); }); } - const adParameters = linearNode?.AdParameters?._cdata; + const adParameters = linearNode?.AdParameters?._cdata._text; const creativeExtensionsNode = creative.CreativeExtensions; let creativeExtensions: CreativeExtension[] = []; if (creativeExtensionsNode) { creativeExtensions = mapArrayOrElem(creativeExtensionsNode.CreativeExtension, creativeExtension => { - const customXML = convert.js2xml(creativeExtension, { compact: true }); + const customXML = this._builder.build(creativeExtension); return new CreativeExtension(customXML, creativeExtension._attributes?.type); }); } const linear = new Linear(duration, mediaFiles, trackingEvents, adParameters, skipoffset); - + return new Creative(creative._attributes.id, parseInt(creative._attributes.sequence), linear); }); const impressions = mapArrayOrElem(inLineNode.Impression, impression => { if (!impression._cdata) return null; - return new Impression(impression._cdata); + return new Impression(impression._cdata._text); }); const inLine = new InLine('', '', impressions, creatives, void 0, void 0, void 0, void 0, void 0, errors, extensions); const newAd = new Ad(ad._attributes?.id, ad._attributes?.sequence ? parseInt(ad._attributes.sequence) : void 0, inLine); diff --git a/packages/vast-parser/src/XmlVAST.ts b/packages/vast-parser/src/XmlVAST.ts index 4ea56de..aabef6c 100644 --- a/packages/vast-parser/src/XmlVAST.ts +++ b/packages/vast-parser/src/XmlVAST.ts @@ -1,9 +1,11 @@ -import type convert from "xml-js"; - -export interface XmlVASTRoot extends convert.ElementCompact { +export interface XmlVASTRoot { VAST: XmlVAST; } +interface XmlTextNode { + _text: string; +} + // 3.2 VAST interface XmlVAST { _attributes: XmlVASTAttributes; @@ -17,7 +19,7 @@ interface XmlVASTAttributes { // 3.2.1 Error (VAST) interface XmlVASTError { - _cdata: string; + _cdata: XmlTextNode; } // 3.3 Ad @@ -67,7 +69,7 @@ interface XmlVASTAdServingId { // 3.4.4 Impression interface XmlVASTImpression { - _cdata: string; + _cdata: XmlTextNode; } // 3.4.5 Category @@ -82,7 +84,7 @@ interface XmlVASTCategoryAttributes { // 3.4.6 Description interface XmlVASTDescription { - _cdata: string; + _cdata: XmlTextNode; } // 3.4.7 Advertiser @@ -123,7 +125,7 @@ interface XmlVASTExpires { // 3.4.11 Error interface XmlVASTError { - _cdata: string; + _cdata: XmlTextNode; } // 3.5 ViewableImpression @@ -140,17 +142,17 @@ interface XmlVASTViewableImpressionAttributes { // 3.5.1 Viewable interface XmlVASTViewable { - _cdata: string; + _cdata: XmlTextNode; } // 3.5.2 NotViewable interface XmlVASTNotViewable { - _cdata: string; + _cdata: XmlTextNode; } // 3.5.3 ViewUndetermined interface XmlVASTViewUndetermined { - _cdata: string; + _cdata: XmlTextNode; } // 3.6 Creatives @@ -200,10 +202,10 @@ interface XmlVASTCreativeExtensions { // 3.7.3 CreativeExtension interface XmlVASTCreativeExtension { _attributes: XmlVASTCreativeExtensionAttributes; - [key: string]: convert.ElementCompact | undefined; + [key: string]: unknown | undefined; } -interface XmlVASTCreativeExtensionAttributes extends convert.Attributes { +interface XmlVASTCreativeExtensionAttributes { type: string; } @@ -233,7 +235,7 @@ interface XmlVASTDuration { // 3.8.2 AdParameters interface XmlVASTAdParameters { _attributes: XmlVASTAdParametersAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTAdParametersAttributes { @@ -251,7 +253,7 @@ interface XmlVASTMediaFiles { // 3.9.1 MediaFile interface XmlVASTMediaFile { _attributes: XmlVASTMediaFileAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTMediaFileAttributes { @@ -274,7 +276,7 @@ interface XmlVASTMediaFileAttributes { // 3.9.2 Mezzanine interface XmlVASTMezzanine { _attributes: XmlVASTMezzanineAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTMezzanineAttributes { @@ -291,7 +293,7 @@ interface XmlVASTMezzanineAttributes { // 3.9.3 InteractiveCreativeFile interface XmlVASTInteractiveCreativeFile { _attributes: XmlVASTInteractiveCreativeFileAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTInteractiveCreativeFileAttributes { @@ -308,7 +310,7 @@ interface XmlVASTClosedCaptionFiles { // 3.9.5 ClosedCaptionFile interface XmlVASTClosedCaptionFile { _attributes?: XmlVASTClosedCaptionFileAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTClosedCaptionFileAttributes { @@ -326,7 +328,7 @@ interface XmlVASTVideoClicks { // 3.10.1 ClickThrough interface XmlVASTClickThrough { _attributes?: XmlVASTClickThroughAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTClickThroughAttributes { @@ -336,7 +338,7 @@ interface XmlVASTClickThroughAttributes { // 3.10.2 ClickTracking interface XmlVASTClickTracking { _attributes?: XmlVASTClickTrackingAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTClickTrackingAttributes { @@ -346,7 +348,7 @@ interface XmlVASTClickTrackingAttributes { // 3.10.3 CustomClick interface XmlVASTCustomClick { _attributes?: XmlVASTCustomClickAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTCustomClickAttributes { @@ -381,7 +383,7 @@ interface XmlVASTIconAttributes { // 3.11.2 IconViewTracking interface XmlVASTIconViewTracking { - _cdata: string; + _cdata: XmlTextNode; } // 3.11.3 IconClicks @@ -393,13 +395,13 @@ interface XmlVASTIconClicks { // 3.11.4 IconClickThrough interface XmlVASTIconClickThrough { - _cdata: string; + _cdata: XmlTextNode; } // 3.11.5 IconClickTracking interface XmlVASTIconClickTracking { _attributes?: XmlVASTIconClickTrackingAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTIconClickTrackingAttributes { @@ -414,7 +416,7 @@ interface XmlVASTIconClickFallbackImages { // 3.11.6.1 IconClickFallbackImage interface XmlVASTIconClickFallbackImage { _attributes: XmlVASTIconClickFallbackImageAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTIconClickFallbackImageAttributes { @@ -460,13 +462,13 @@ interface XmlVASTNonLinearInInLine extends XmlVASTNonLinear { // 3.12.2 NonLinearClickThrough interface XmlVASTNonLinearClickThrough { - _cdata: string; + _cdata: XmlTextNode; } // 3.12.3 NonLinearClickTracking interface XmlVASTNonLinearClickTracking { _attributes?: XmlVASTNonLinearClickTrackingAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTNonLinearClickTrackingAttributes { @@ -485,7 +487,7 @@ interface XmlVASTWrapper { interface XmlVASTTracking { _attributes: XmlVASTTrackingAttributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVASTTrackingAttributes { @@ -500,7 +502,7 @@ interface XmlVASTExtensions { interface XmlVASTExtension { _attributes?: XmlVASTExtensionAttributes; - [key: string]: convert.ElementCompact | undefined; + [key: string]: unknown | undefined; } interface XmlVASTExtensionAttributes { diff --git a/packages/vast-parser/tests/index.test.ts b/packages/vast-parser/tests/index.test.ts index 0a1c991..8167b59 100644 --- a/packages/vast-parser/tests/index.test.ts +++ b/packages/vast-parser/tests/index.test.ts @@ -150,7 +150,7 @@ test('parse normal VAST', () => { ad = parsedVAST.ads[0]; assert.is(ad.sequence, 1); - + inLine = ad.inLine!; assert.is(inLine.errors.length, 2, 'Ad 1 should have 2 errors'); assert.is(inLine.errors[0], 'https://example.com/vast/preroll-ad-1/ad/1/error/1'); @@ -160,6 +160,7 @@ test('parse normal VAST', () => { assert.is(inLine.extensions[0].customXML, ''); assert.is(inLine.extensions[1].type, 'type-2', 'AD 1 extension 1 should have type "type-2"'); assert.is(inLine.extensions[1].customXML, '423710', ''); + assert.is(inLine.creatives[0].linear!.duration, 10); assert.is(inLine.creatives[0].linear!.mediaFiles[0].bitrate, 4000); assert.is(inLine.creatives[0].linear!.mediaFiles[5].bitrate, 120); @@ -186,4 +187,4 @@ test('parse no AdBreak VAST', () => { assert.is(parsedVAST.ads.length, 0); }); -test.run(); \ No newline at end of file +test.run(); diff --git a/packages/vmap-parser/package.json b/packages/vmap-parser/package.json index 1ec85eb..1b496cb 100644 --- a/packages/vmap-parser/package.json +++ b/packages/vmap-parser/package.json @@ -20,13 +20,13 @@ "test": "uvu -r ts-node/register tests" }, "dependencies": { - "xml-js": "^1.6.11" + "fast-xml-parser": "^4.5.0" }, "devDependencies": { "buffer": "^6.0.3", "stream": "^0.0.2", - "ts-node": "^10.9.2", "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "typescript": "^5.3.3", "uvu": "^0.5.6", "webpack": "^5.89.0", diff --git a/packages/vmap-parser/src/VMAPParser.ts b/packages/vmap-parser/src/VMAPParser.ts index 274f088..513a953 100644 --- a/packages/vmap-parser/src/VMAPParser.ts +++ b/packages/vmap-parser/src/VMAPParser.ts @@ -1,4 +1,4 @@ -import converter from 'xml-js'; +import { XMLParser } from 'fast-xml-parser'; import VMAP from "./VMAP"; import type { XmlVMAPRoot } from './XmlVMAP'; import AdTagURI from './AdTagURI'; @@ -21,6 +21,14 @@ export default class VMAPParser { */ constructor(vmap: string) { this._originalVMAP = vmap; + this._parser = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: '', + attributesGroupName: '_attributes', + cdataPropName: '_cdata', + alwaysCreateTextNode: true, + textNodeName: '_text', + }); } /** @@ -33,14 +41,15 @@ export default class VMAPParser { } private _originalVMAP: string | undefined; + private _parser: XMLParser; private _parseVMAP(vmap: string): VMAP | null { - const vmapRoot: XmlVMAPRoot = converter.xml2js(vmap, { compact: true }) as XmlVMAPRoot; + const vmapRoot: XmlVMAPRoot = this._parser.parse(vmap) as XmlVMAPRoot; const vmapObj = vmapRoot['vmap:VMAP']; const adBreaks = mapArrayOrElem(vmapObj['vmap:AdBreak'], adBreak => { if (!adBreak) return null; const adSources = mapArrayOrElem(adBreak['vmap:AdSource'], adSource => { - const cdata = adSource['vmap:AdTagURI']?._cdata; + const cdata = adSource['vmap:AdTagURI']?._cdata._text; const templateType = adSource['vmap:AdTagURI']?._attributes.templateType; if (cdata && templateType) { console.log(cdata); @@ -50,7 +59,7 @@ export default class VMAPParser { } return null; }); - + const newAdBreak = new AdBreak( adBreak._attributes.timeOffset, adBreak._attributes.breakType, diff --git a/packages/vmap-parser/src/XmlVMAP.ts b/packages/vmap-parser/src/XmlVMAP.ts index 7621dcd..422a2ef 100644 --- a/packages/vmap-parser/src/XmlVMAP.ts +++ b/packages/vmap-parser/src/XmlVMAP.ts @@ -2,6 +2,10 @@ export interface XmlVMAPRoot { ['vmap:VMAP']: XmlVMAP; } +interface XmlTextNode { + _text: string; +} + interface XmlVMAP { _attributes: XmlVMAPAttributes; ['vmap:AdBreak']?: XmlVMAPAdBreak | XmlVMAPAdBreak[]; @@ -51,7 +55,7 @@ interface XmlVMAPAdData { interface XmlVMAPAdTagURI { _attributes: XmlVMAPAdTagURIAtributes; - _cdata: string; + _cdata: XmlTextNode; } interface XmlVMAPAdTagURIAtributes { @@ -67,4 +71,4 @@ interface XmlVMAPTracking { interface XmlVMAPTrackingAttributes { event: string; -} \ No newline at end of file +}