webgal-parser 4.5.2 → 4.5.16

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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # WebGAL Parser Changelog
2
+
3
+ ## [4.5.13] - 2025-07-05
4
+
5
+ ### Fixed
6
+ - Fixed interface consistency between `webgal-parser` and main WebGAL project
7
+ - Added missing trailing comma in `commandType` enum to match WebGAL main project
8
+ - Updated `ISceneData` interface to use `ISceneEntry` instead of `sceneEntry` for better type consistency
9
+
10
+ ### Added
11
+ - Added `ISceneEntry` interface as compatibility alias for `sceneEntry`
12
+ - Added comprehensive test cases for new animation parameters:
13
+ - `wait` command testing
14
+ - `changeFigure` with `duration`, `enter`, `exit` parameters
15
+ - `changeBg` with animation and transform parameters
16
+
17
+ ### Improved
18
+ - Enhanced type safety and consistency with main WebGAL project
19
+ - Better test coverage for animation-related features
20
+ - Improved interface documentation
21
+
22
+ ### Technical Notes
23
+ - The parser already supports all animation parameters (`duration`, `enter`, `exit`, `transform`) through its generic argument parsing system
24
+ - No changes to core parsing logic were needed - the parser was already compatible with the latest WebGAL features
25
+ - This update focuses on interface consistency and test coverage improvements
26
+
27
+ ## [4.5.12] - Previous Version
28
+ - Previous stable release with `wait` command support
@@ -37,52 +37,62 @@ var commandType;
37
37
  commandType[commandType["setTransition"] = 30] = "setTransition";
38
38
  commandType[commandType["getUserInput"] = 31] = "getUserInput";
39
39
  commandType[commandType["applyStyle"] = 32] = "applyStyle";
40
+ commandType[commandType["wait"] = 33] = "wait";
40
41
  })(commandType || (commandType = {}));
41
42
 
42
43
  const SCRIPT_CONFIG = [
43
- { scriptString: 'intro', scriptType: commandType.intro },
44
+ { scriptString: 'say', scriptType: commandType.say },
44
45
  { scriptString: 'changeBg', scriptType: commandType.changeBg },
45
46
  { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
47
+ { scriptString: 'bgm', scriptType: commandType.bgm },
48
+ { scriptString: 'playVideo', scriptType: commandType.video },
49
+ { scriptString: 'pixiPerform', scriptType: commandType.pixi },
50
+ { scriptString: 'pixiInit', scriptType: commandType.pixiInit },
51
+ { scriptString: 'intro', scriptType: commandType.intro },
46
52
  { scriptString: 'miniAvatar', scriptType: commandType.miniAvatar },
47
53
  { scriptString: 'changeScene', scriptType: commandType.changeScene },
48
54
  { scriptString: 'choose', scriptType: commandType.choose },
49
55
  { scriptString: 'end', scriptType: commandType.end },
50
- { scriptString: 'bgm', scriptType: commandType.bgm },
51
- { scriptString: 'playVideo', scriptType: commandType.video },
52
56
  {
53
57
  scriptString: 'setComplexAnimation',
54
58
  scriptType: commandType.setComplexAnimation,
55
59
  },
56
60
  { scriptString: 'setFilter', scriptType: commandType.setFilter },
57
- { scriptString: 'pixiInit', scriptType: commandType.pixiInit },
58
- { scriptString: 'pixiPerform', scriptType: commandType.pixi },
59
61
  { scriptString: 'label', scriptType: commandType.label },
60
62
  { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel },
63
+ { scriptString: 'chooseLabel', scriptType: commandType.chooseLabel },
61
64
  { scriptString: 'setVar', scriptType: commandType.setVar },
65
+ { scriptString: 'if', scriptType: commandType.if },
62
66
  { scriptString: 'callScene', scriptType: commandType.callScene },
63
67
  { scriptString: 'showVars', scriptType: commandType.showVars },
64
68
  { scriptString: 'unlockCg', scriptType: commandType.unlockCg },
65
69
  { scriptString: 'unlockBgm', scriptType: commandType.unlockBgm },
66
- { scriptString: 'say', scriptType: commandType.say },
67
70
  { scriptString: 'filmMode', scriptType: commandType.filmMode },
68
- { scriptString: 'callScene', scriptType: commandType.callScene },
69
71
  { scriptString: 'setTextbox', scriptType: commandType.setTextbox },
70
72
  { scriptString: 'setAnimation', scriptType: commandType.setAnimation },
71
73
  { scriptString: 'playEffect', scriptType: commandType.playEffect },
74
+ { scriptString: 'setTempAnimation', scriptType: commandType.setTempAnimation },
75
+ // comment?
76
+ { scriptString: 'setTransform', scriptType: commandType.setTransform },
77
+ { scriptString: 'setTransition', scriptType: commandType.setTransition },
78
+ { scriptString: 'getUserInput', scriptType: commandType.getUserInput },
72
79
  { scriptString: 'applyStyle', scriptType: commandType.applyStyle },
80
+ { scriptString: 'wait', scriptType: commandType.wait },
73
81
  ];
74
82
  const ADD_NEXT_ARG_LIST = [
75
83
  commandType.bgm,
76
84
  commandType.pixi,
77
85
  commandType.pixiInit,
86
+ commandType.miniAvatar,
78
87
  commandType.label,
79
88
  commandType.if,
80
- commandType.miniAvatar,
81
89
  commandType.setVar,
82
- commandType.unlockBgm,
83
90
  commandType.unlockCg,
91
+ commandType.unlockBgm,
84
92
  commandType.filmMode,
85
93
  commandType.playEffect,
94
+ commandType.setTransition,
95
+ commandType.applyStyle,
86
96
  ];
87
97
 
88
98
  /**
@@ -118,10 +128,10 @@ function argsParser(argsRaw, assetSetter) {
118
128
  });
119
129
  rawArgsList.forEach((e) => {
120
130
  const equalSignIndex = e.indexOf('=');
121
- let argName = e.slice(0, equalSignIndex);
122
- let argValue = e.slice(equalSignIndex + 1);
131
+ let argName = e.slice(0, equalSignIndex).trim();
132
+ let argValue = e.slice(equalSignIndex + 1).trim();
123
133
  if (equalSignIndex < 0) {
124
- argName = e;
134
+ argName = e.trim();
125
135
  argValue = undefined;
126
136
  }
127
137
  // 判断是不是语音参数
@@ -182,7 +192,7 @@ function configLineParser(inputLine) {
182
192
  };
183
193
  }
184
194
  // 截取命令
185
- const getCommandResult = /:/.exec(newSentenceRaw);
195
+ const getCommandResult = /\s*:\s*/.exec(newSentenceRaw);
186
196
  // 没有command
187
197
  if (getCommandResult === null) {
188
198
  command = '';
@@ -304,12 +314,12 @@ const contentParser = (contentRaw, type, assetSetter) => {
304
314
  }
305
315
  };
306
316
  function getChooseContent(contentRaw, assetSetter) {
307
- const chooseList = contentRaw.split('|');
317
+ const chooseList = contentRaw.split(/(?<!\\)\|/);
308
318
  const chooseKeyList = [];
309
319
  const chooseValueList = [];
310
320
  for (const e of chooseList) {
311
- chooseKeyList.push(e.split(':')[0] ?? '');
312
- chooseValueList.push(e.split(':')[1] ?? '');
321
+ chooseKeyList.push(e.split(/(?<!\\):/)[0] ?? '');
322
+ chooseValueList.push(e.split(/(?<!\\):/)[1] ?? '');
313
323
  }
314
324
  const parsedChooseList = chooseValueList.map((e) => {
315
325
  if (e.match(/\./)) {
@@ -437,14 +447,16 @@ const scriptParser = (sentenceRaw, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG
437
447
  let commandRaw;
438
448
  // 正式开始解析
439
449
  // 去分号
440
- let newSentenceRaw = sentenceRaw.split(/(?<!\\);/)[0];
450
+ const commentSplit = sentenceRaw.split(/(?<!\\);/);
451
+ let newSentenceRaw = commentSplit[0].trim();
441
452
  newSentenceRaw = newSentenceRaw.replaceAll('\\;', ';');
453
+ const sentenceComment = commentSplit[1] ?? '';
442
454
  if (newSentenceRaw === '') {
443
455
  // 注释提前返回
444
456
  return {
445
457
  command: commandType.comment,
446
458
  commandRaw: 'comment',
447
- content: sentenceRaw.split(';')[1] ?? '',
459
+ content: sentenceComment.trim(),
448
460
  args: [{ key: 'next', value: true }],
449
461
  sentenceAssets: [],
450
462
  subScene: [], // 语句携带的子场景
@@ -1761,7 +1773,7 @@ var uniqWith_1 = uniqWith;
1761
1773
  * @return {IScene} 解析后的场景
1762
1774
  */
1763
1775
  const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
1764
- const rawSentenceList = rawScene.split('\n'); // 原始句子列表
1776
+ const rawSentenceList = rawScene.replaceAll('\r', '').split('\n'); // 原始句子列表
1765
1777
  // 去分号留到后面去做了,现在注释要单独处理
1766
1778
  const rawSentenceListWithoutEmpty = rawSentenceList;
1767
1779
  // .map((sentence) => sentence.split(";")[0])
@@ -1817,6 +1829,198 @@ function parseCSS(css) {
1817
1829
  return [result, specialRules.trim()];
1818
1830
  }
1819
1831
 
1832
+ /**
1833
+ * Preprocessor for scene text.
1834
+ *
1835
+ * Use two-pass to generate a new scene text that concats multiline sequences
1836
+ * into a single line and add placeholder lines to preserve the original number
1837
+ * of lines.
1838
+ *
1839
+ * @param sceneText The original scene text
1840
+ * @returns The processed scene text
1841
+ */
1842
+ function sceneTextPreProcess(sceneText) {
1843
+ let lines = sceneText.replaceAll('\r', '').split('\n');
1844
+ lines = sceneTextPreProcessPassOne(lines);
1845
+ lines = sceneTextPreProcessPassTwo(lines);
1846
+ return lines.join('\n');
1847
+ }
1848
+ /**
1849
+ * Pass one.
1850
+ *
1851
+ * Add escape character to all lines that should be multiline.
1852
+ *
1853
+ * @param lines The original lines
1854
+ * @returns The processed lines
1855
+ */
1856
+ function sceneTextPreProcessPassOne(lines) {
1857
+ const processedLines = [];
1858
+ let lastLineIsMultiline = false;
1859
+ let thisLineIsMultiline = false;
1860
+ for (const line of lines) {
1861
+ thisLineIsMultiline = false;
1862
+ if (canBeMultiline(line)) {
1863
+ thisLineIsMultiline = true;
1864
+ }
1865
+ if (shouldNotBeMultiline(line, lastLineIsMultiline)) {
1866
+ thisLineIsMultiline = false;
1867
+ }
1868
+ if (thisLineIsMultiline) {
1869
+ processedLines[processedLines.length - 1] += '\\';
1870
+ }
1871
+ processedLines.push(line);
1872
+ lastLineIsMultiline = thisLineIsMultiline;
1873
+ }
1874
+ return processedLines;
1875
+ }
1876
+ function canBeMultiline(line) {
1877
+ if (!line.startsWith(' ')) {
1878
+ return false;
1879
+ }
1880
+ const trimmedLine = line.trimStart();
1881
+ return trimmedLine.startsWith('|') || trimmedLine.startsWith('-');
1882
+ }
1883
+ /**
1884
+ * Logic to check if a line should not be multiline.
1885
+ *
1886
+ * @param line The line to check
1887
+ * @returns If the line should not be multiline
1888
+ */
1889
+ function shouldNotBeMultiline(line, lastLineIsMultiline) {
1890
+ if (!lastLineIsMultiline && isEmptyLine(line)) {
1891
+ return true;
1892
+ }
1893
+ // Custom logic: if the line contains -concat, it should not be multiline
1894
+ if (line.indexOf('-concat') !== -1) {
1895
+ return true;
1896
+ }
1897
+ return false;
1898
+ }
1899
+ function isEmptyLine(line) {
1900
+ return line.trim() === '';
1901
+ }
1902
+ /**
1903
+ * Pass two.
1904
+ *
1905
+ * Traverse the lines to
1906
+ * - remove escape characters
1907
+ * - add placeholder lines to preserve the original number of lines.
1908
+ *
1909
+ * @param lines The lines in pass one
1910
+ * @returns The processed lines
1911
+ */
1912
+ function sceneTextPreProcessPassTwo(lines) {
1913
+ const processedLines = [];
1914
+ let currentMultilineContent = "";
1915
+ let placeHolderLines = [];
1916
+ function concat(line) {
1917
+ let trimmed = line.trim();
1918
+ if (trimmed.startsWith('-')) {
1919
+ trimmed = " " + trimmed;
1920
+ }
1921
+ currentMultilineContent = currentMultilineContent + trimmed;
1922
+ placeHolderLines.push(placeholderLine(line));
1923
+ }
1924
+ for (const line of lines) {
1925
+ console.log(line);
1926
+ if (line.endsWith('\\')) {
1927
+ const trueLine = line.slice(0, -1);
1928
+ if (currentMultilineContent === "") {
1929
+ // first line
1930
+ currentMultilineContent = trueLine;
1931
+ }
1932
+ else {
1933
+ // middle line
1934
+ concat(trueLine);
1935
+ }
1936
+ continue;
1937
+ }
1938
+ if (currentMultilineContent !== "") {
1939
+ // end line
1940
+ concat(line);
1941
+ processedLines.push(currentMultilineContent);
1942
+ processedLines.push(...placeHolderLines);
1943
+ placeHolderLines = [];
1944
+ currentMultilineContent = "";
1945
+ continue;
1946
+ }
1947
+ processedLines.push(line);
1948
+ }
1949
+ return processedLines;
1950
+ }
1951
+ /**
1952
+ * Placeholder Line. Adding this line preserves the original number of lines
1953
+ * in the scene text, so that it can be compatible with the graphical editor.
1954
+ *
1955
+ * @param content The original content on this line
1956
+ * @returns The placeholder line
1957
+ */
1958
+ function placeholderLine(content = "") {
1959
+ return ";_WEBGAL_LINE_BREAK_" + content;
1960
+ }
1961
+ // export function sceneTextPreProcess(sceneText: string): string {
1962
+ // const lines = sceneText.replaceAll('\r', '').split('\n');
1963
+ // const processedLines: string[] = [];
1964
+ // let lastNonMultilineIndex = -1;
1965
+ // let isInMultilineSequence = false;
1966
+ // function isMultiline(line: string): boolean {
1967
+ // if (!line.startsWith(' ')) return false;
1968
+ // const trimmedLine = line.trimStart();
1969
+ // return trimmedLine.startsWith('|') || trimmedLine.startsWith('-');
1970
+ // }
1971
+ // for (let i = 0; i < lines.length; i++) {
1972
+ // const line = lines[i];
1973
+ // if (line.trim() === '') {
1974
+ // // Empty line handling
1975
+ // if (isInMultilineSequence) {
1976
+ // // Check if the next line is a multiline line
1977
+ // let isStillInMulti = false;
1978
+ // for (let j = i + 1; j < lines.length; j++) {
1979
+ // const lookForwardLine = lines[j] || '';
1980
+ // // 遇到正常语句了,直接中断
1981
+ // if (lookForwardLine.trim() !== '' && !isMultiline(lookForwardLine)) {
1982
+ // isStillInMulti = false;
1983
+ // break;
1984
+ // }
1985
+ // // 必须找到后面接的是参数,并且中间没有遇到任何正常语句才行
1986
+ // if (lookForwardLine.trim() !== '' && isMultiline(lookForwardLine)) {
1987
+ // isStillInMulti = true;
1988
+ // break;
1989
+ // }
1990
+ // }
1991
+ // if (isStillInMulti) {
1992
+ // // Still within a multiline sequence
1993
+ // processedLines.push(';_WEBGAL_LINE_BREAK_');
1994
+ // } else {
1995
+ // // End of multiline sequence
1996
+ // isInMultilineSequence = false;
1997
+ // processedLines.push(line);
1998
+ // }
1999
+ // } else {
2000
+ // // Preserve empty lines outside of multiline sequences
2001
+ // processedLines.push(line);
2002
+ // }
2003
+ // } else if (isMultiline(line)) {
2004
+ // // Multiline statement handling
2005
+ // if (lastNonMultilineIndex >= 0) {
2006
+ // // Concatenate to the previous non-multiline statement
2007
+ // const trimedLine = line.trimStart();
2008
+ // const addBlank = trimedLine.startsWith('-') ? ' ' : '';
2009
+ // processedLines[lastNonMultilineIndex] += addBlank + trimedLine;
2010
+ // }
2011
+ // // Add the special comment line
2012
+ // processedLines.push(';_WEBGAL_LINE_BREAK_' + line);
2013
+ // isInMultilineSequence = true;
2014
+ // } else {
2015
+ // // Non-multiline statement handling
2016
+ // processedLines.push(line);
2017
+ // lastNonMultilineIndex = processedLines.length - 1;
2018
+ // isInMultilineSequence = false;
2019
+ // }
2020
+ // }
2021
+ // return processedLines.join('\n');
2022
+ // }
2023
+
1820
2024
  class SceneParser {
1821
2025
  assetsPrefetcher;
1822
2026
  assetSetter;
@@ -1863,3 +2067,4 @@ class SceneParser {
1863
2067
  exports.ADD_NEXT_ARG_LIST = ADD_NEXT_ARG_LIST;
1864
2068
  exports.SCRIPT_CONFIG = SCRIPT_CONFIG;
1865
2069
  exports.default = SceneParser;
2070
+ exports.sceneTextPreProcess = sceneTextPreProcess;
@@ -3,6 +3,7 @@ import { WebgalConfig } from './configParser/configParser';
3
3
  import { fileType } from './interface/assets';
4
4
  import { IAsset } from './interface/sceneInterface';
5
5
  import { IWebGALStyleObj } from "./styleParser";
6
+ import { sceneTextPreProcess } from "./sceneTextPreProcessor";
6
7
  export default class SceneParser {
7
8
  private readonly assetsPrefetcher;
8
9
  private readonly assetSetter;
@@ -22,3 +23,4 @@ export default class SceneParser {
22
23
  parseScssToWebgalStyleObj(scssString: string): IWebGALStyleObj;
23
24
  }
24
25
  export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG };
26
+ export { sceneTextPreProcess };
@@ -7,3 +7,9 @@ export interface sceneEntry {
7
7
  sceneUrl: string;
8
8
  continueLine: number;
9
9
  }
10
+ /**
11
+ * 场景栈条目接口 (兼容性别名)
12
+ * @interface ISceneEntry
13
+ */
14
+ export interface ISceneEntry extends sceneEntry {
15
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 语句类型
3
3
  */
4
- import { sceneEntry } from './runtimeInterface';
4
+ import { ISceneEntry } from './runtimeInterface';
5
5
  import { fileType } from './assets';
6
6
  export declare enum commandType {
7
7
  say = 0,
@@ -36,7 +36,8 @@ export declare enum commandType {
36
36
  setTransform = 29,
37
37
  setTransition = 30,
38
38
  getUserInput = 31,
39
- applyStyle = 32
39
+ applyStyle = 32,
40
+ wait = 33
40
41
  }
41
42
  /**
42
43
  * 单个参数接口
@@ -85,7 +86,7 @@ export interface IScene {
85
86
  */
86
87
  export interface ISceneData {
87
88
  currentSentenceId: number;
88
- sceneStack: Array<sceneEntry>;
89
+ sceneStack: Array<ISceneEntry>;
89
90
  currentScene: IScene;
90
91
  }
91
92
  /**
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Preprocessor for scene text.
3
+ *
4
+ * Use two-pass to generate a new scene text that concats multiline sequences
5
+ * into a single line and add placeholder lines to preserve the original number
6
+ * of lines.
7
+ *
8
+ * @param sceneText The original scene text
9
+ * @returns The processed scene text
10
+ */
11
+ export declare function sceneTextPreProcess(sceneText: string): string;
@@ -3,6 +3,7 @@ import { WebgalConfig } from './configParser/configParser';
3
3
  import { fileType } from './interface/assets';
4
4
  import { IAsset } from './interface/sceneInterface';
5
5
  import { IWebGALStyleObj } from "./styleParser";
6
+ import { sceneTextPreProcess } from "./sceneTextPreProcessor";
6
7
  export default class SceneParser {
7
8
  private readonly assetsPrefetcher;
8
9
  private readonly assetSetter;
@@ -22,3 +23,4 @@ export default class SceneParser {
22
23
  parseScssToWebgalStyleObj(scssString: string): IWebGALStyleObj;
23
24
  }
24
25
  export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG };
26
+ export { sceneTextPreProcess };