ddex-json-codec 0.3.1 → 0.4.2

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.d.mts CHANGED
@@ -350,7 +350,7 @@ declare function xmlToJson(xml: string): DdexMessage;
350
350
  /**
351
351
  * DdexMessageをXML文字列に変換する
352
352
  */
353
- declare function jsonToXml(message: DdexMessage, version?: ErnVersion): string;
353
+ declare function jsonToXml(message: DdexMessage): string;
354
354
  //#endregion
355
355
  //#region src/version/detect.d.ts
356
356
  /**
@@ -364,4 +364,28 @@ declare function jsonToXml(message: DdexMessage, version?: ErnVersion): string;
364
364
  */
365
365
  declare function detectVersion(xml: string): ErnVersion;
366
366
  //#endregion
367
- export { type Artist, type ArtistRole, type CLine, type Contributor, type DdexMessage, type DdexMessage38, type DdexMessage4, type DdexMessageBase, type Deal, type DealTerms, type DisplayArtist, type DisplayTitle, type ErnMajorVersion, type ErnVersion, type ErnVersion38, type ErnVersion4, type FileDetails, type Genre, type HashSum, type Image, type Image38, type Image4, type ImageBase, type ImageDetailsByTerritory, type ImageId, type IndirectResourceContributor, type MessageHeader, type MessageParty, type PLine, type Party, type PartyName, type Release, type Release38, type Release4, type ReleaseBase, type ReleaseDeal, type ReleaseDetailsByTerritory, type ReleaseId, type ReleaseResourceReference, type ResourceContributor, type ResourceGroup, type SoundRecording, type SoundRecording38, type SoundRecording4, type SoundRecordingBase, type SoundRecordingDetailsByTerritory, type TechnicalImageDetails, type TechnicalSoundRecordingDetails, type TextWithAttribute, type Title, type TrackRelease, type Usage, detectVersion, jsonToXml, xmlToJson };
367
+ //#region src/converter/version/index.d.ts
368
+ interface ConversionWarning {
369
+ type: 'field_dropped' | 'structure_changed';
370
+ path: string;
371
+ message: string;
372
+ }
373
+ interface ConversionResult {
374
+ result: DdexMessage;
375
+ warnings: ConversionWarning[];
376
+ }
377
+ /**
378
+ * DdexMessage をターゲットメジャーバージョンに変換する
379
+ * パッチバージョンは各メジャーの最新が使われる (3.8.3 / 4.3.2)
380
+ */
381
+ declare function convertDdexMessage(message: DdexMessage, target: ErnMajorVersion): ConversionResult;
382
+ //#endregion
383
+ //#region src/index.d.ts
384
+ /**
385
+ * DDEX XML文字列をターゲットメジャーバージョンに変換する
386
+ */
387
+ declare function convertDdexVersion(xml: string, target: ErnMajorVersion): ConversionResult & {
388
+ xml: string;
389
+ };
390
+ //#endregion
391
+ export { type Artist, type ArtistRole, type CLine, type Contributor, type ConversionResult, type ConversionWarning, type DdexMessage, type DdexMessage38, type DdexMessage4, type DdexMessageBase, type Deal, type DealTerms, type DisplayArtist, type DisplayTitle, type ErnMajorVersion, type ErnVersion, type ErnVersion38, type ErnVersion4, type FileDetails, type Genre, type HashSum, type Image, type Image38, type Image4, type ImageBase, type ImageDetailsByTerritory, type ImageId, type IndirectResourceContributor, type MessageHeader, type MessageParty, type PLine, type Party, type PartyName, type Release, type Release38, type Release4, type ReleaseBase, type ReleaseDeal, type ReleaseDetailsByTerritory, type ReleaseId, type ReleaseResourceReference, type ResourceContributor, type ResourceGroup, type SoundRecording, type SoundRecording38, type SoundRecording4, type SoundRecordingBase, type SoundRecordingDetailsByTerritory, type TechnicalImageDetails, type TechnicalSoundRecordingDetails, type TextWithAttribute, type Title, type TrackRelease, type Usage, convertDdexMessage, convertDdexVersion, xmlToJson as ddexToJson, detectVersion as detectDdexVersion, jsonToXml as jsonToDdex };
package/dist/index.mjs CHANGED
@@ -1518,9 +1518,349 @@ function xmlToJson(xml) {
1518
1518
  /**
1519
1519
  * DdexMessageをXML文字列に変換する
1520
1520
  */
1521
- function jsonToXml(message, version) {
1522
- const targetVersion = version ?? message.ernVersion;
1523
- return ConverterFactory.createJsonToXmlBuilder(targetVersion).build(message, targetVersion);
1521
+ function jsonToXml(message) {
1522
+ return ConverterFactory.createJsonToXmlBuilder(message.ernVersion).build(message, message.ernVersion);
1524
1523
  }
1525
1524
  //#endregion
1526
- export { detectVersion, jsonToXml, xmlToJson };
1525
+ //#region src/converter/version/index.ts
1526
+ const LATEST_PATCH = {
1527
+ "3.8": "3.8.3",
1528
+ "4": "4.3.2"
1529
+ };
1530
+ /**
1531
+ * DdexMessage をターゲットメジャーバージョンに変換する
1532
+ * パッチバージョンは各メジャーの最新が使われる (3.8.3 / 4.3.2)
1533
+ */
1534
+ function convertDdexMessage(message, target) {
1535
+ if (getMajorVersion(message.ernVersion) === target) return {
1536
+ result: message,
1537
+ warnings: []
1538
+ };
1539
+ if (target === "4") return convert38To4(message, LATEST_PATCH["4"]);
1540
+ return convert4To38(message, LATEST_PATCH["3.8"]);
1541
+ }
1542
+ function convert38To4(msg, target) {
1543
+ const warnings = [];
1544
+ const partyMap = /* @__PURE__ */ new Map();
1545
+ const resourceList = msg.resourceList.map((sr, i) => convertSoundRecording38To4(sr, i, partyMap, warnings));
1546
+ const imageList = msg.imageList?.map((img) => ({
1547
+ resourceReference: img.resourceReference,
1548
+ type: img.type,
1549
+ imageId: img.imageId,
1550
+ parentalWarningType: img.detailsByTerritory?.[0]?.parentalWarningType,
1551
+ technicalDetails: img.detailsByTerritory?.[0]?.technicalDetails
1552
+ }));
1553
+ const releaseList = msg.releaseList.map((r, i) => convertRelease38To4(r, i, partyMap, warnings));
1554
+ const dealList = convertDealList38To4(msg.dealList, warnings);
1555
+ const partyList = Array.from(partyMap.values());
1556
+ return {
1557
+ result: {
1558
+ ernVersion: target,
1559
+ messageHeader: msg.messageHeader,
1560
+ updateIndicator: msg.updateIndicator,
1561
+ resourceList,
1562
+ imageList,
1563
+ releaseList,
1564
+ dealList,
1565
+ partyList: partyList.length > 0 ? partyList : void 0
1566
+ },
1567
+ warnings
1568
+ };
1569
+ }
1570
+ function convertSoundRecording38To4(sr, index, partyMap, warnings) {
1571
+ const dbt = sr.detailsByTerritory?.[0];
1572
+ if (sr.detailsByTerritory && sr.detailsByTerritory.length > 1) warnings.push({
1573
+ type: "structure_changed",
1574
+ path: `resourceList[${index}].detailsByTerritory`,
1575
+ message: `${sr.detailsByTerritory.length} territories collapsed to first territory`
1576
+ });
1577
+ const displayArtists = (dbt?.displayArtists ?? sr.displayArtists).map((da) => ensurePartyReference(da, partyMap));
1578
+ const contributors = convertResourceContributorsToContributors(dbt?.resourceContributors, partyMap);
1579
+ if (dbt?.indirectResourceContributors?.length) warnings.push({
1580
+ type: "field_dropped",
1581
+ path: `resourceList[${index}].detailsByTerritory[0].indirectResourceContributors`,
1582
+ message: "IndirectResourceContributors dropped (no equivalent in ERN 4)"
1583
+ });
1584
+ return {
1585
+ resourceReference: sr.resourceReference,
1586
+ type: sr.type,
1587
+ soundRecordingId: sr.soundRecordingId,
1588
+ displayArtists,
1589
+ duration: sr.duration,
1590
+ creationDate: sr.creationDate,
1591
+ languageOfPerformance: sr.languageOfPerformance,
1592
+ pLine: dbt?.pLine ?? sr.pLine,
1593
+ displayTitleText: sr.referenceTitle?.titleText,
1594
+ displayTitles: convertTitlesToDisplayTitles(dbt?.titles, dbt?.territoryCode?.[0]),
1595
+ contributors: contributors.length > 0 ? contributors : void 0
1596
+ };
1597
+ }
1598
+ function convertRelease38To4(r, index, partyMap, warnings) {
1599
+ const dbt = r.detailsByTerritory?.[0];
1600
+ if (r.detailsByTerritory && r.detailsByTerritory.length > 1) warnings.push({
1601
+ type: "structure_changed",
1602
+ path: `releaseList[${index}].detailsByTerritory`,
1603
+ message: `${r.detailsByTerritory.length} territories collapsed to first territory`
1604
+ });
1605
+ const displayArtists = (dbt?.displayArtists ?? r.displayArtists).map((da) => ensurePartyReference(da, partyMap));
1606
+ const releaseLabelReferences = dbt?.labelName ? [ensureLabelPartyReference(dbt.labelName, partyMap)] : void 0;
1607
+ return {
1608
+ releaseReference: r.releaseReference,
1609
+ releaseType: r.releaseType,
1610
+ releaseId: r.releaseId,
1611
+ displayArtists,
1612
+ releaseResourceReferences: r.releaseResourceReferences,
1613
+ duration: r.duration,
1614
+ pLine: r.pLine,
1615
+ cLine: r.cLine,
1616
+ displayTitleText: r.referenceTitle?.titleText,
1617
+ displayTitles: convertTitlesToDisplayTitles(dbt?.titles, dbt?.territoryCode?.[0]),
1618
+ releaseLabelReferences,
1619
+ genre: dbt?.genre,
1620
+ parentalWarningType: dbt?.parentalWarningType,
1621
+ resourceGroup: dbt?.resourceGroup ?? r.resourceGroup
1622
+ };
1623
+ }
1624
+ function convertDealList38To4(dealList, warnings) {
1625
+ return dealList.map((rd, i) => ({
1626
+ ...rd,
1627
+ deals: rd.deals.filter((d, j) => {
1628
+ if (d.dealTerms.takeDown) {
1629
+ warnings.push({
1630
+ type: "structure_changed",
1631
+ path: `dealList[${i}].deals[${j}]`,
1632
+ message: "Deal with takeDown=true removed (ERN 4 uses deal absence for takedown)"
1633
+ });
1634
+ return false;
1635
+ }
1636
+ return true;
1637
+ }).map((d) => ({
1638
+ ...d,
1639
+ dealTerms: convertDealTerms38To4(d.dealTerms)
1640
+ }))
1641
+ }));
1642
+ }
1643
+ function convertDealTerms38To4(dt) {
1644
+ return {
1645
+ commercialModelType: dt.commercialModelType,
1646
+ useTypes: dt.usage?.useTypes,
1647
+ territoryCode: dt.territoryCode,
1648
+ validityPeriod: dt.validityPeriod,
1649
+ priceInformation: dt.priceInformation
1650
+ };
1651
+ }
1652
+ function convert4To38(msg, target) {
1653
+ const warnings = [];
1654
+ const partyIndex = /* @__PURE__ */ new Map();
1655
+ if (msg.partyList) for (const p of msg.partyList) partyIndex.set(p.partyReference, p);
1656
+ const resourceList = msg.resourceList.map((sr, i) => convertSoundRecording4To38(sr, i, partyIndex, warnings));
1657
+ const imageList = msg.imageList?.map((img) => ({
1658
+ resourceReference: img.resourceReference,
1659
+ type: img.type,
1660
+ imageId: img.imageId,
1661
+ detailsByTerritory: [{
1662
+ territoryCode: ["Worldwide"],
1663
+ parentalWarningType: img.parentalWarningType,
1664
+ technicalDetails: img.technicalDetails
1665
+ }]
1666
+ }));
1667
+ const releaseList = msg.releaseList.map((r, i) => convertRelease4To38(r, i, partyIndex, warnings));
1668
+ if (msg.trackReleaseList?.length) warnings.push({
1669
+ type: "field_dropped",
1670
+ path: "trackReleaseList",
1671
+ message: `${msg.trackReleaseList.length} TrackRelease(s) dropped (not available in ERN 3.8)`
1672
+ });
1673
+ if (msg.partyList?.some((p) => p.partyId?.length)) warnings.push({
1674
+ type: "field_dropped",
1675
+ path: "partyList[].partyId",
1676
+ message: "PartyId (ISNI, IPN etc.) dropped (no storage in ERN 3.8 inline PartyName)"
1677
+ });
1678
+ const dealList = msg.dealList.map((rd) => ({
1679
+ ...rd,
1680
+ deals: rd.deals.map((d) => ({
1681
+ ...d,
1682
+ dealTerms: convertDealTerms4To38(d.dealTerms)
1683
+ }))
1684
+ }));
1685
+ return {
1686
+ result: {
1687
+ ernVersion: target,
1688
+ messageHeader: msg.messageHeader,
1689
+ updateIndicator: msg.updateIndicator,
1690
+ resourceList,
1691
+ imageList,
1692
+ releaseList,
1693
+ dealList
1694
+ },
1695
+ warnings
1696
+ };
1697
+ }
1698
+ function convertSoundRecording4To38(sr, _index, partyIndex, _warnings) {
1699
+ const displayArtists = sr.displayArtists.map((da) => resolveInlineArtist(da, partyIndex));
1700
+ const referenceTitle = sr.displayTitleText ? { titleText: sr.displayTitleText } : sr.displayTitles?.[0] ? {
1701
+ titleText: sr.displayTitles[0].titleText,
1702
+ subTitle: sr.displayTitles[0].subTitle
1703
+ } : void 0;
1704
+ const titles = convertDisplayTitlesToTitles(sr.displayTitles);
1705
+ const resourceContributors = sr.contributors?.map((c) => resolveContributorToResourceContributor(c, partyIndex));
1706
+ const dbt = {
1707
+ territoryCode: ["Worldwide"],
1708
+ displayArtists,
1709
+ titles,
1710
+ pLine: sr.pLine
1711
+ };
1712
+ if (resourceContributors?.length) dbt.resourceContributors = resourceContributors;
1713
+ return {
1714
+ resourceReference: sr.resourceReference,
1715
+ type: sr.type,
1716
+ soundRecordingId: sr.soundRecordingId,
1717
+ displayArtists,
1718
+ duration: sr.duration,
1719
+ creationDate: sr.creationDate,
1720
+ languageOfPerformance: sr.languageOfPerformance,
1721
+ pLine: sr.pLine,
1722
+ referenceTitle,
1723
+ detailsByTerritory: [dbt]
1724
+ };
1725
+ }
1726
+ function convertRelease4To38(r, _index, partyIndex, _warnings) {
1727
+ const displayArtists = r.displayArtists.map((da) => resolveInlineArtist(da, partyIndex));
1728
+ const referenceTitle = r.displayTitleText ? { titleText: r.displayTitleText } : r.displayTitles?.[0] ? {
1729
+ titleText: r.displayTitles[0].titleText,
1730
+ subTitle: r.displayTitles[0].subTitle
1731
+ } : void 0;
1732
+ const titles = convertDisplayTitlesToTitles(r.displayTitles);
1733
+ let labelName;
1734
+ if (r.releaseLabelReferences?.[0]) labelName = partyIndex.get(r.releaseLabelReferences[0])?.partyName?.[0]?.fullName ?? r.releaseLabelReferences[0];
1735
+ const dbt = {
1736
+ territoryCode: ["Worldwide"],
1737
+ displayArtists,
1738
+ titles,
1739
+ labelName,
1740
+ genre: r.genre,
1741
+ parentalWarningType: r.parentalWarningType,
1742
+ resourceGroup: r.resourceGroup
1743
+ };
1744
+ return {
1745
+ releaseReference: r.releaseReference,
1746
+ releaseType: r.releaseType,
1747
+ releaseId: r.releaseId,
1748
+ displayArtists,
1749
+ releaseResourceReferences: r.releaseResourceReferences,
1750
+ duration: r.duration,
1751
+ pLine: r.pLine,
1752
+ cLine: r.cLine,
1753
+ referenceTitle,
1754
+ detailsByTerritory: [dbt],
1755
+ resourceGroup: r.resourceGroup
1756
+ };
1757
+ }
1758
+ function convertDealTerms4To38(dt) {
1759
+ return {
1760
+ commercialModelType: dt.commercialModelType,
1761
+ usage: dt.useTypes ? { useTypes: dt.useTypes } : void 0,
1762
+ territoryCode: dt.territoryCode,
1763
+ validityPeriod: dt.validityPeriod,
1764
+ priceInformation: dt.priceInformation
1765
+ };
1766
+ }
1767
+ function convertResourceContributorsToContributors(contributors, partyMap) {
1768
+ if (!contributors?.length) return [];
1769
+ return contributors.map((c) => {
1770
+ const ref = generatePartyReference(c.name);
1771
+ if (!partyMap.has(ref)) partyMap.set(ref, {
1772
+ partyReference: ref,
1773
+ partyName: [{ fullName: c.name }]
1774
+ });
1775
+ return {
1776
+ contributorPartyReference: ref,
1777
+ name: c.name,
1778
+ role: c.role,
1779
+ sequenceNumber: c.sequenceNumber
1780
+ };
1781
+ });
1782
+ }
1783
+ function generatePartyReference(name) {
1784
+ return `P${name.replace(/[^a-zA-Z0-9]/g, "") || "Unknown"}`;
1785
+ }
1786
+ function ensurePartyReference(da, partyMap) {
1787
+ if (da.artist.partyReference) return da;
1788
+ const ref = generatePartyReference(da.artist.name);
1789
+ if (!partyMap.has(ref)) {
1790
+ const partyNames = da.artist.names?.length ? da.artist.names : [{ fullName: da.artist.name }];
1791
+ partyMap.set(ref, {
1792
+ partyReference: ref,
1793
+ partyName: partyNames
1794
+ });
1795
+ }
1796
+ return {
1797
+ ...da,
1798
+ artist: {
1799
+ ...da.artist,
1800
+ partyReference: ref
1801
+ }
1802
+ };
1803
+ }
1804
+ function ensureLabelPartyReference(labelName, partyMap) {
1805
+ const ref = generatePartyReference(labelName);
1806
+ if (!partyMap.has(ref)) partyMap.set(ref, {
1807
+ partyReference: ref,
1808
+ partyName: [{ fullName: labelName }]
1809
+ });
1810
+ return ref;
1811
+ }
1812
+ function resolveInlineArtist(da, partyIndex) {
1813
+ if (!da.artist.partyReference) return da;
1814
+ const party = partyIndex.get(da.artist.partyReference);
1815
+ const name = party?.partyName?.[0]?.fullName ?? da.artist.name;
1816
+ const names = party?.partyName && party.partyName.length > 1 ? party.partyName : da.artist.names;
1817
+ return {
1818
+ ...da,
1819
+ artist: {
1820
+ name,
1821
+ names,
1822
+ roles: da.artist.roles
1823
+ }
1824
+ };
1825
+ }
1826
+ function resolveContributorToResourceContributor(c, partyIndex) {
1827
+ return {
1828
+ name: partyIndex.get(c.contributorPartyReference)?.partyName?.[0]?.fullName ?? c.name ?? c.contributorPartyReference,
1829
+ role: c.role,
1830
+ sequenceNumber: c.sequenceNumber
1831
+ };
1832
+ }
1833
+ function convertTitlesToDisplayTitles(titles, territoryCode) {
1834
+ if (!titles?.length) return void 0;
1835
+ return titles.map((t, i) => ({
1836
+ titleText: t.titleText,
1837
+ subTitle: t.subTitle,
1838
+ applicableTerritoryCode: territoryCode ?? "Worldwide",
1839
+ languageAndScriptCode: t.languageAndScriptCode,
1840
+ isDefault: i === 0 ? true : void 0
1841
+ }));
1842
+ }
1843
+ function convertDisplayTitlesToTitles(displayTitles) {
1844
+ if (!displayTitles?.length) return void 0;
1845
+ return displayTitles.map((dt) => ({
1846
+ titleText: dt.titleText,
1847
+ subTitle: dt.subTitle,
1848
+ titleType: "DisplayTitle",
1849
+ languageAndScriptCode: dt.languageAndScriptCode
1850
+ }));
1851
+ }
1852
+ //#endregion
1853
+ //#region src/index.ts
1854
+ /**
1855
+ * DDEX XML文字列をターゲットメジャーバージョンに変換する
1856
+ */
1857
+ function convertDdexVersion(xml, target) {
1858
+ const { result, warnings } = convertDdexMessage(xmlToJson(xml), target);
1859
+ return {
1860
+ result,
1861
+ xml: jsonToXml(result),
1862
+ warnings
1863
+ };
1864
+ }
1865
+ //#endregion
1866
+ export { convertDdexMessage, convertDdexVersion, xmlToJson as ddexToJson, detectVersion as detectDdexVersion, jsonToXml as jsonToDdex };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ddex-json-codec",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.4.2",
5
5
  "description": "DDEX ERN XML ↔ JSON converter for TypeScript",
6
6
  "license": "MIT",
7
7
  "author": "kazhs",
@@ -31,6 +31,13 @@
31
31
  "files": [
32
32
  "dist"
33
33
  ],
34
+ "scripts": {
35
+ "build": "vp pack",
36
+ "dev": "vp pack --watch",
37
+ "test": "vp test",
38
+ "check": "vp check",
39
+ "prepublishOnly": "pnpm run build"
40
+ },
34
41
  "dependencies": {
35
42
  "fast-xml-parser": "^5.3.0"
36
43
  },
@@ -42,13 +49,14 @@
42
49
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest",
43
50
  "vite-plus": "latest"
44
51
  },
52
+ "pnpm": {
53
+ "overrides": {
54
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
55
+ "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
56
+ }
57
+ },
58
+ "packageManager": "pnpm@10.32.1",
45
59
  "engines": {
46
60
  "node": ">=20"
47
- },
48
- "scripts": {
49
- "build": "vp pack",
50
- "dev": "vp pack --watch",
51
- "test": "vp test",
52
- "check": "vp check"
53
61
  }
54
- }
62
+ }