forked from mirror/ObjToSchematic
Fixed parsing and added basic testing
This commit is contained in:
parent
c845ee947d
commit
a99fa2b452
7
jestconfig.json
Normal file
7
jestconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"lint": "eslint --fix ./src/**/*.ts && eslint --fix ./tools/**/*.ts",
|
||||
"debug": "tsc && electron ./dist/main.js --enable-logging",
|
||||
"build": "npm run lint && tsc",
|
||||
"test": "jest --config jestconfig.json",
|
||||
"start": "npm run build && electron ./dist/main.js --enable-logging",
|
||||
"atlas": "npx ts-node tools/build-atlas.ts",
|
||||
"palette": "npx ts-node tools/build-palette.ts",
|
||||
@ -26,6 +27,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/LucasDower/ObjToSchematic#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/jquery": "^3.5.6",
|
||||
"@types/obj-file-parser": "^0.5.0",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
@ -39,7 +41,9 @@
|
||||
"electron-packager": "^15.2.0",
|
||||
"eslint": "^8.7.0",
|
||||
"images": "^3.2.3",
|
||||
"jest": "^27.5.1",
|
||||
"prompt": "^1.2.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"ts-node": "^10.1.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { IImporter } from '../importer';
|
||||
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial, Tri } from '../mesh';
|
||||
import { Vector3 } from '../vector';
|
||||
import { UV, ASSERT, RGB, CustomError, LOG } from '../util';
|
||||
import { UV, ASSERT, RGB, CustomError, LOG, REGEX_NUMBER, RegExpBuilder, REGEX_NZ_ANY, LOG_ERROR } from '../util';
|
||||
import { UI } from '../ui/layout';
|
||||
import { checkFractional, checkNaN } from '../math';
|
||||
|
||||
@ -20,19 +20,31 @@ export class ObjImporter extends IImporter {
|
||||
private _objPath?: path.ParsedPath;
|
||||
private _objParsers = [
|
||||
{
|
||||
regex: /mtllib (?<path>.*\.mtl)/,
|
||||
// e.g. 'mtllib my_file.mtl'
|
||||
regex: new RegExpBuilder().add(/mtllib/).add(/ /).add(REGEX_NZ_ANY, 'path').toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._mtlLibs.push(match.path);
|
||||
this._mtlLibs.push(match.path.trim());
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /usemtl (?<name>.*)/,
|
||||
// e.g. 'usemtl my_material'
|
||||
regex: new RegExpBuilder().add(/usemtl/).add(/ /).add(REGEX_NZ_ANY, 'name').toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._currentMaterialName = match.name;
|
||||
this._currentMaterialName = match.name.trim();
|
||||
ASSERT(this._currentMaterialName);
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /v (?<x>.*) (?<y>.*) (?<z>.*)/,
|
||||
// e.g. 'v 0.123 0.456 0.789'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/v/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'x')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'y')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'z')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const x = parseFloat(match.x);
|
||||
const y = parseFloat(match.y);
|
||||
@ -42,7 +54,14 @@ export class ObjImporter extends IImporter {
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /vt (?<u>.*) (?<v>.*)/,
|
||||
// e.g. 'vt 0.123 0.456'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/vt/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'u')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'v')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const u = parseFloat(match.u);
|
||||
const v = parseFloat(match.v);
|
||||
@ -51,16 +70,27 @@ export class ObjImporter extends IImporter {
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*)\/(?<iuvx>.*)\/.* (?<iy>.*)\/(?<iuvy>.*)\/.* (?<iz>.*)\/(?<iuvz>.*)\/.* (?<iw>.*)\/(?<iuvw>.*)\//,
|
||||
// e.g. 'f 1/2/3 4/5/6 7/8/9 10/11/12' or 'f 1/2 3/4 5/6 7/8'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/f/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'xIndex').addMany(['/'], true).add(REGEX_NUMBER, 'xtIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'yIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ytIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'zIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ztIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'wIndex').addMany(['/'], true).add(REGEX_NUMBER, 'wtIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
const iW = parseInt(match.iw) - 1;
|
||||
const iUVx = parseInt(match.iuvx) - 1;
|
||||
const iUVy = parseInt(match.iuvy) - 1;
|
||||
const iUVz = parseInt(match.iuvz) - 1;
|
||||
const iUVw = parseInt(match.iuvw) - 1;
|
||||
const iX = parseInt(match.xIndex) - 1;
|
||||
const iY = parseInt(match.yIndex) - 1;
|
||||
const iZ = parseInt(match.zIndex) - 1;
|
||||
const iW = parseInt(match.wIndex) - 1;
|
||||
const iUVx = parseInt(match.xtIndex) - 1;
|
||||
const iUVy = parseInt(match.ytIndex) - 1;
|
||||
const iUVz = parseInt(match.ztIndex) - 1;
|
||||
const iUVw = parseInt(match.wtIndex) - 1;
|
||||
checkNaN(iX, iY, iZ, iW);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
@ -84,14 +114,23 @@ export class ObjImporter extends IImporter {
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*)\/(?<iuvx>.*)\/.* (?<iy>.*)\/(?<iuvy>.*)\/.* (?<iz>.*)\/(?<iuvz>.*)\//,
|
||||
// e.g. f 1/2/3 4/5/6 7/8/9 or 1/2 3/4 5/6
|
||||
regex: new RegExpBuilder()
|
||||
.add(/f/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'xIndex').addMany(['/'], true).add(REGEX_NUMBER, 'xtIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'yIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ytIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'zIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ztIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
const iUVx = parseInt(match.iuvx) - 1;
|
||||
const iUVy = parseInt(match.iuvy) - 1;
|
||||
const iUVz = parseInt(match.iuvz) - 1;
|
||||
const iX = parseInt(match.xIndex) - 1;
|
||||
const iY = parseInt(match.yIndex) - 1;
|
||||
const iZ = parseInt(match.zIndex) - 1;
|
||||
const iUVx = parseInt(match.xtIndex) - 1;
|
||||
const iUVy = parseInt(match.ytIndex) - 1;
|
||||
const iUVz = parseInt(match.ztIndex) - 1;
|
||||
checkNaN(iX, iY, iZ);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
@ -105,25 +144,6 @@ export class ObjImporter extends IImporter {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /f (?<ix>.*) (?<iy>.*) (?<iz>.*)/,
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const iX = parseInt(match.ix) - 1;
|
||||
const iY = parseInt(match.iy) - 1;
|
||||
const iZ = parseInt(match.iz) - 1;
|
||||
checkNaN(iX, iY, iZ);
|
||||
ASSERT(this._currentMaterialName);
|
||||
this._tris.push({
|
||||
iX: iX,
|
||||
iY: iY,
|
||||
iZ: iZ,
|
||||
iXUV: iX,
|
||||
iYUV: iY,
|
||||
iZUV: iZ,
|
||||
material: this._currentMaterialName,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
private _currentColour: RGB = RGB.black;
|
||||
@ -131,16 +151,26 @@ export class ObjImporter extends IImporter {
|
||||
private _materialReady: boolean = false;
|
||||
private _mtlParsers = [
|
||||
{
|
||||
regex: /newmtl (?<name>.*)/,
|
||||
// e.g. 'newmtl my_material'
|
||||
regex: new RegExpBuilder().add(/newmtl/).add(REGEX_NZ_ANY, 'name').toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._addCurrentMaterial();
|
||||
this._currentMaterialName = match.name;
|
||||
this._currentMaterialName = match.name.trim();
|
||||
this._currentTexture = '';
|
||||
this._materialReady = false;
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /Kd (?<r>.*) (?<g>.*) (?<b>.*)/,
|
||||
// e.g. 'Kd 0.123 0.456 0.789'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/Kd/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'r')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'g')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'b')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const r = parseFloat(match.r);
|
||||
const g = parseFloat(match.g);
|
||||
@ -152,9 +182,10 @@ export class ObjImporter extends IImporter {
|
||||
},
|
||||
},
|
||||
{
|
||||
regex: /map_Kd (?<path>.*)/,
|
||||
// e.g. 'map_Kd my/path/to/file.png'
|
||||
regex: new RegExpBuilder().add(/map_Kd/).add(REGEX_NZ_ANY, 'path').toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
let mtlPath = match.path;
|
||||
let mtlPath = match.path.trim();
|
||||
if (!path.isAbsolute(mtlPath)) {
|
||||
ASSERT(this._objPath);
|
||||
mtlPath = path.join(this._objPath.dir, mtlPath);
|
||||
@ -208,7 +239,7 @@ export class ObjImporter extends IImporter {
|
||||
}
|
||||
|
||||
private _parseOBJLine(line: string) {
|
||||
const essentialTokens = ['mtllib ', 'uselib ', 'v ', 'vt ', 'f '];
|
||||
const essentialTokens = ['mtllib ', 'usemtl ', 'v ', 'vt ', 'f '];
|
||||
|
||||
for (const parser of this._objParsers) {
|
||||
const match = parser.regex.exec(line);
|
||||
@ -216,6 +247,7 @@ export class ObjImporter extends IImporter {
|
||||
try {
|
||||
parser.delegate(match.groups);
|
||||
} catch (error) {
|
||||
LOG_ERROR('Caught', error);
|
||||
if (error instanceof CustomError) {
|
||||
throw new CustomError(`Failed attempt to parse '${line}', because '${error.message}'`);
|
||||
}
|
||||
@ -228,7 +260,7 @@ export class ObjImporter extends IImporter {
|
||||
return line.startsWith(token);
|
||||
});
|
||||
if (beginsWithEssentialToken) {
|
||||
throw new CustomError(`Failed to parse essential token for ${line}`);
|
||||
throw new CustomError(`Failed to parse essential token for <b>${line}</b>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
17
src/mesh.ts
17
src/mesh.ts
@ -58,6 +58,16 @@ export class Mesh {
|
||||
}
|
||||
|
||||
private _checkMesh() {
|
||||
// TODO: Check indices exist
|
||||
|
||||
if (this.vertices.length === 0) {
|
||||
throw new CustomError('Loaded mesh has no vertices');
|
||||
}
|
||||
|
||||
if (this.tris.length === 0) {
|
||||
throw new CustomError('Loaded mesh has no triangles');
|
||||
}
|
||||
|
||||
// Check UVs are inside [0, 1]
|
||||
for (const uv of this.uvs) {
|
||||
if (uv.u < 0.0 || uv.u > 1.0) {
|
||||
@ -70,6 +80,10 @@ export class Mesh {
|
||||
}
|
||||
|
||||
private _checkMaterials() {
|
||||
if (Object.keys(this.materials).length === 0) {
|
||||
throw new CustomError('Loaded mesh has no materials');
|
||||
}
|
||||
|
||||
// Check used materials exist
|
||||
let wasRemapped = false;
|
||||
let debugName = (Math.random() + 1).toString(36).substring(7);
|
||||
@ -77,13 +91,16 @@ export class Mesh {
|
||||
debugName = (Math.random() + 1).toString(36).substring(7);
|
||||
}
|
||||
|
||||
const missingMaterials = new Set<string>();
|
||||
for (const tri of this.tris) {
|
||||
if (!(tri.material in this.materials)) {
|
||||
missingMaterials.add(tri.material);
|
||||
wasRemapped = true;
|
||||
tri.material = debugName;
|
||||
}
|
||||
}
|
||||
if (wasRemapped) {
|
||||
LOG_WARN('Triangles use these materials but they were not found', missingMaterials);
|
||||
AppContext.Get.addWarning('Some materials were not loaded correctly');
|
||||
this.materials[debugName] = {
|
||||
type: MaterialType.solid,
|
||||
|
@ -64,13 +64,13 @@ export class Texture {
|
||||
const y = uv.v * this._image.height;
|
||||
|
||||
const xL = Math.floor(x);
|
||||
const xU = Math.ceil(x);
|
||||
const xU = xL + 1;
|
||||
const yL = Math.floor(y);
|
||||
const yU = Math.ceil(y);
|
||||
const yU = yL + 1;
|
||||
|
||||
const u = wayThrough(x, xL, xU);
|
||||
const v = wayThrough(y, yL, yU);
|
||||
ASSERT(u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0);
|
||||
ASSERT(u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0, `UV out of range (${u}, ${v})`);
|
||||
|
||||
const A = this._getFromXY(xL, yU).toVector3();
|
||||
const B = this._getFromXY(xU, yU).toVector3();
|
||||
|
73
src/util.ts
73
src/util.ts
@ -129,9 +129,33 @@ export function ASSERT(condition: any, errorMessage = 'Assertion Failed'): asser
|
||||
export const LOG = console.log;
|
||||
export const LOG_WARN = console.warn;
|
||||
export const LOG_ERROR = console.error;
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
/** Regex for non-zero whitespace */
|
||||
export const REGEX_NZ_WS = /[ \t]+/;
|
||||
|
||||
/** Regex for number */
|
||||
export const REGEX_NUMBER = /[0-9\.\-]+/;
|
||||
|
||||
export const REGEX_NZ_ANY = /.+/;
|
||||
|
||||
export function regexCapture(identifier: string, regex: RegExp) {
|
||||
return new RegExp(`(?<${identifier}>${regex.source}`);
|
||||
}
|
||||
|
||||
export function regexOptional(regex: RegExp) {
|
||||
return new RegExp(`(${regex})?`);
|
||||
}
|
||||
|
||||
export function buildRegex(...args: (string | RegExp)[]) {
|
||||
return new RegExp(args.map((r) => {
|
||||
if (r instanceof RegExp) {
|
||||
return r.source;
|
||||
}
|
||||
return r;
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
export class CustomError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg);
|
||||
@ -149,3 +173,50 @@ export class CustomWarning extends Error {
|
||||
export function fileExists(absolutePath: string) {
|
||||
return fs.existsSync(absolutePath);
|
||||
}
|
||||
|
||||
export class RegExpBuilder {
|
||||
private _components: string[];
|
||||
|
||||
public constructor() {
|
||||
this._components = [];
|
||||
}
|
||||
|
||||
public add(item: string | RegExp, capture?: string, optional: boolean = false): RegExpBuilder {
|
||||
let regex: string;
|
||||
if (item instanceof RegExp) {
|
||||
regex = item.source;
|
||||
} else {
|
||||
regex = item;
|
||||
}
|
||||
if (capture) {
|
||||
regex = `(?<${capture}>${regex})`;
|
||||
}
|
||||
if (optional) {
|
||||
regex = `(${regex})?`;
|
||||
}
|
||||
this._components.push(regex);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addMany(items: (string | RegExp)[], optional: boolean = false): RegExpBuilder {
|
||||
let toAdd: string = '';
|
||||
for (const item of items) {
|
||||
if (item instanceof RegExp) {
|
||||
toAdd += item.source;
|
||||
} else {
|
||||
toAdd += item;
|
||||
}
|
||||
}
|
||||
this._components.push(optional ? `(${toAdd})?` : toAdd);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addNonzeroWhitespace(): RegExpBuilder {
|
||||
this.add(REGEX_NZ_WS);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toRegExp(): RegExp {
|
||||
return new RegExp(this._components.join(''));
|
||||
}
|
||||
}
|
||||
|
137
test/util.test.ts
Normal file
137
test/util.test.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import { ASSERT, RegExpBuilder, REGEX_NUMBER, REGEX_NZ_ANY } from '../src/util';
|
||||
|
||||
test('RegExpBuilder', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/hello/)
|
||||
.toRegExp();
|
||||
expect(regex.test('hello')).toBe(true);
|
||||
expect(regex.test('there')).toBe(false);
|
||||
});
|
||||
|
||||
test('RegExpBuilder REGEX_NUMBER', () => {
|
||||
const tests = [
|
||||
{ f: '0', s: 0 },
|
||||
{ f: '0.0', s: 0.0 },
|
||||
{ f: '-0.0', s: -0.0 },
|
||||
{ f: '1', s: 1 },
|
||||
{ f: '1.0', s: 1.0 },
|
||||
{ f: '-1.0', s: -1.0 },
|
||||
];
|
||||
for (const t of tests) {
|
||||
const temp = REGEX_NUMBER.exec(t.f);
|
||||
ASSERT(temp !== null);
|
||||
expect(parseFloat(temp[0])).toEqual(t.s);
|
||||
}
|
||||
});
|
||||
|
||||
test('RegExpBuilder Required-whitespace', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/hello/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(/there/)
|
||||
.toRegExp();
|
||||
expect(regex.test('hello there')).toBe(true);
|
||||
expect(regex.test('hello there')).toBe(true);
|
||||
expect(regex.test('hellothere')).toBe(false);
|
||||
});
|
||||
|
||||
test('RegExpBuilder Optional', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/hello/)
|
||||
.addNonzeroWhitespace()
|
||||
.addMany([/there/], true)
|
||||
.toRegExp();
|
||||
expect(regex.test('hello there')).toBe(true);
|
||||
expect(regex.test('hello there')).toBe(true);
|
||||
expect(regex.test('hello ')).toBe(true);
|
||||
expect(regex.test('hello')).toBe(false);
|
||||
});
|
||||
|
||||
test('RegExpBuilder Capture', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/[0-9]+/, 'myNumber')
|
||||
.toRegExp();
|
||||
const exec = regex.exec('1234');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups).toHaveProperty('myNumber');
|
||||
expect(exec.groups['myNumber']).toBe('1234');
|
||||
}
|
||||
});
|
||||
|
||||
test('RegExpBuilder Capture-multiple', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/[0-9]+/, 'x')
|
||||
.addNonzeroWhitespace()
|
||||
.add(/[0-9]+/, 'y')
|
||||
.addNonzeroWhitespace()
|
||||
.add(/[0-9]+/, 'z')
|
||||
.toRegExp();
|
||||
|
||||
const exec = regex.exec('123 456 789');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups).toHaveProperty('x');
|
||||
expect(exec.groups).toHaveProperty('y');
|
||||
expect(exec.groups).toHaveProperty('z');
|
||||
expect(exec.groups['x']).toBe('123');
|
||||
expect(exec.groups['y']).toBe('456');
|
||||
expect(exec.groups['z']).toBe('789');
|
||||
}
|
||||
});
|
||||
|
||||
test('RegExpBuilder Capture-multiple', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/f/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'xIndex').addMany(['/'], true).add(REGEX_NUMBER, 'xtIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'yIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ytIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'zIndex').addMany(['/'], true).add(REGEX_NUMBER, 'ztIndex', true).addMany(['/', REGEX_NUMBER], true)
|
||||
.toRegExp();
|
||||
|
||||
let exec = regex.exec('f 1/2/3 4/5/6 7/8/9');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups['xIndex']).toBe('1');
|
||||
expect(exec.groups['xtIndex']).toBe('2');
|
||||
expect(exec.groups['yIndex']).toBe('4');
|
||||
expect(exec.groups['ytIndex']).toBe('5');
|
||||
expect(exec.groups['zIndex']).toBe('7');
|
||||
expect(exec.groups['ztIndex']).toBe('8');
|
||||
}
|
||||
|
||||
exec = regex.exec('f 1//3 4//6 7//9');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups['xIndex']).toBe('1');
|
||||
expect(exec.groups['xtIndex']).toBeUndefined();
|
||||
expect(exec.groups['yIndex']).toBe('4');
|
||||
expect(exec.groups['ytIndex']).toBeUndefined();
|
||||
expect(exec.groups['zIndex']).toBe('7');
|
||||
expect(exec.groups['ztIndex']).toBeUndefined();
|
||||
}
|
||||
|
||||
exec = regex.exec('f 1 4 7');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups['xIndex']).toBe('1');
|
||||
expect(exec.groups['yIndex']).toBe('4');
|
||||
expect(exec.groups['zIndex']).toBe('7');
|
||||
}
|
||||
});
|
||||
|
||||
test('RegExpBuilder Capture-multiple', () => {
|
||||
const regex = new RegExpBuilder()
|
||||
.add(/usemtl/)
|
||||
.add(/ /)
|
||||
.add(REGEX_NZ_ANY, 'path')
|
||||
.toRegExp();
|
||||
|
||||
const exec = regex.exec('usemtl hellothere.txt');
|
||||
expect(exec).toHaveProperty('groups');
|
||||
if (exec !== null && exec.groups) {
|
||||
expect(exec.groups['path']).toBe('hellothere.txt');
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user