wt-huatu 1.3.4
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/Readme.md +223 -0
- package/component/HuatuComponentCache.js +89 -0
- package/component/HuatuComponentDef.js +249 -0
- package/component/HuatuComponentPlacement.js +172 -0
- package/component/HuatuComponentPort.js +58 -0
- package/component/IHuatuComponent.js +2 -0
- package/for-node/node-cli-funcs.js +36 -0
- package/geometry/AngDeg.js +68 -0
- package/geometry/Angle.js +77 -0
- package/geometry/Bezier.js +623 -0
- package/geometry/Circle.js +143 -0
- package/geometry/Ellipse.js +546 -0
- package/geometry/EllipticalArc.js +337 -0
- package/geometry/IGeometry.js +1 -0
- package/geometry/Intersection.js +601 -0
- package/geometry/Line.js +136 -0
- package/geometry/LineSeg.js +179 -0
- package/geometry/Point.js +88 -0
- package/geometry/PolyLine.js +97 -0
- package/geometry/Polygon.js +122 -0
- package/geometry/Rect.js +149 -0
- package/geometry/Ring.js +91 -0
- package/geometry/SegmentModel.js +206 -0
- package/geometry/Triangle.js +168 -0
- package/geometry/Vector.js +92 -0
- package/huatu/Huatu.js +4279 -0
- package/huatu/HuatuAngleMark.js +400 -0
- package/huatu/HuatuBlock.js +26 -0
- package/huatu/HuatuClipPath.js +29 -0
- package/huatu/HuatuCompDrawing.js +77 -0
- package/huatu/HuatuDimMark.js +579 -0
- package/huatu/HuatuDrawing.js +185 -0
- package/huatu/HuatuDrawingGroup.js +52 -0
- package/huatu/HuatuJob.js +347 -0
- package/huatu/HuatuLabel.js +311 -0
- package/huatu/HuatuLabelStyle.js +190 -0
- package/huatu/HuatuLayer.js +10 -0
- package/huatu/HuatuMarker.js +209 -0
- package/huatu/HuatuParser.js +435 -0
- package/huatu/HuatuPath.js +131 -0
- package/huatu/HuatuPathSeries.js +51 -0
- package/huatu/HuatuPattern.js +54 -0
- package/huatu/HuatuPoint.js +20 -0
- package/huatu/HuatuPointSeries.js +67 -0
- package/huatu/HuatuStyle.js +394 -0
- package/huatu/HuatuSymbol.js +81 -0
- package/huatu/HuatuSymbolArray.js +235 -0
- package/huatu/HuatuTransform.js +113 -0
- package/huatu/HuatuVar.js +17 -0
- package/huatu/HuatuYml.js +271 -0
- package/huatu/IHuatu.js +2 -0
- package/huatu/IHuatuYml.js +1 -0
- package/huatu/IndentParsingStack.js +82 -0
- package/index.d.ts +1747 -0
- package/index.js +247 -0
- package/math/Complex.js +72 -0
- package/math/Matrix.js +31 -0
- package/math/NumberRange.js +38 -0
- package/math/VarFuncs.js +24 -0
- package/math/WtMath.js +217 -0
- package/package.json +34 -0
- package/path/PathBezier2.js +75 -0
- package/path/PathBezier3.js +89 -0
- package/path/PathCircle.js +82 -0
- package/path/PathConfig.js +81 -0
- package/path/PathContinuousSegmentedPath.js +390 -0
- package/path/PathEllipse.js +99 -0
- package/path/PathEllipticalArc.js +111 -0
- package/path/PathGeneric.js +59 -0
- package/path/PathLine.js +75 -0
- package/path/PathLines.js +46 -0
- package/path/PathPolygon.js +142 -0
- package/path/PathPolyline.js +266 -0
- package/path/PathRect.js +125 -0
- package/path/PathRing.js +51 -0
- package/path/PathSegmentedPath.js +199 -0
- package/path/PathTriangle.js +90 -0
- package/presets/marker.js +37 -0
- package/presets/pattern.js +85 -0
- package/shape/SvgShapeArrowHead.js +92 -0
- package/shape/SvgShapeCircle.js +26 -0
- package/shape/SvgShapeCross.js +43 -0
- package/shape/SvgShapeEllipse.js +28 -0
- package/shape/SvgShapeGeneric.js +50 -0
- package/shape/SvgShapeHeart.js +40 -0
- package/shape/SvgShapeIsoscelesTriangle.js +52 -0
- package/shape/SvgShapePolygon.js +39 -0
- package/shape/SvgShapeRect.js +26 -0
- package/shape/SvgShapeRhombus.js +27 -0
- package/shape/SvgShapeStar.js +103 -0
- package/svg/ISvg.js +1 -0
- package/svg/SvgBBox.js +149 -0
- package/svg/SvgConstants.js +14 -0
- package/svg/SvgGradient.js +97 -0
- package/svg/SvgMarker.js +221 -0
- package/svg/SvgPattern.js +228 -0
- package/svg/SvgPoint.js +33 -0
- package/svg/SvgPointSet.js +41 -0
- package/templates/lines.js +35 -0
- package/tools/gen-id.js +2 -0
- package/tools/html.js +2 -0
- package/tools/katex.js +75 -0
- package/tools/rand-str.js +122 -0
- package/tools/regex.js +15 -0
- package/tools/utils.js +438 -0
- package/tools/webColor.js +700 -0
- package/wire/HuatuNet.js +22 -0
- package/wire/HuatuWire.js +415 -0
- package/wire/HuatuWireDrawing.js +17 -0
- package/wire/IHuatuWire.js +1 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { HuatuDrawing } from "./HuatuDrawing.js";
|
|
2
|
+
import Vector from "../geometry/Vector.js";
|
|
3
|
+
import { AngDeg } from "../geometry/AngDeg.js";
|
|
4
|
+
import { HuatuDrawingGroup } from "./HuatuDrawingGroup.js";
|
|
5
|
+
export class HuatuSymbolArray extends HuatuDrawingGroup {
|
|
6
|
+
static METHODS = ["line", "2d", "polar"];
|
|
7
|
+
symbol;
|
|
8
|
+
arrayMethod = "line";
|
|
9
|
+
startPoint; // Start point will not be used by Polar arraying method
|
|
10
|
+
deltaX = 0;
|
|
11
|
+
deltaY = 0;
|
|
12
|
+
/**
|
|
13
|
+
* This count will be shared when Array specified as Polar or Line type
|
|
14
|
+
* If specified as 2D, then countX and countY will work.
|
|
15
|
+
*/
|
|
16
|
+
count = 1;
|
|
17
|
+
countX = 1;
|
|
18
|
+
countY = 1;
|
|
19
|
+
center;
|
|
20
|
+
startDeg;
|
|
21
|
+
deltaDeg;
|
|
22
|
+
alignCenter;
|
|
23
|
+
transforms = [];
|
|
24
|
+
constructor(name, symbol, desc) {
|
|
25
|
+
super(name, desc);
|
|
26
|
+
// Symbol array will not accept any transforming
|
|
27
|
+
this.acceptTransform = false;
|
|
28
|
+
this.symbol = symbol;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the Linear generation of the Symbol Array
|
|
32
|
+
* @param count The Count to place the symbol
|
|
33
|
+
* @param dx Interval in X-axis
|
|
34
|
+
* @param dy Interval in Y-axis
|
|
35
|
+
* @param scale Scale factor of the Symbol, could be a number, or a func like Si = f(i) where i is the growing index
|
|
36
|
+
*/
|
|
37
|
+
configLine(startPoint, count, dx, dy, scale, rotDeg) {
|
|
38
|
+
this.startPoint = startPoint;
|
|
39
|
+
// Clear element buffer at first
|
|
40
|
+
this.elements = [];
|
|
41
|
+
this.arrayMethod = "line";
|
|
42
|
+
count = Math.floor(count);
|
|
43
|
+
this.count = count >= 1 ? count : 1;
|
|
44
|
+
this.deltaX = dx ?? 0;
|
|
45
|
+
this.deltaY = dy ?? 0;
|
|
46
|
+
if (scale && typeof scale === "number") {
|
|
47
|
+
scale = scale > 0 ? scale : undefined; // non-positive scale will be discarded
|
|
48
|
+
}
|
|
49
|
+
for (let i = 0; i < this.count; i++) {
|
|
50
|
+
const transform = {
|
|
51
|
+
x: this.startPoint.x + i * this.deltaX,
|
|
52
|
+
y: this.startPoint.y + i * this.deltaY
|
|
53
|
+
};
|
|
54
|
+
if (scale) {
|
|
55
|
+
if (typeof scale === "function") {
|
|
56
|
+
try {
|
|
57
|
+
let newScale = scale(i);
|
|
58
|
+
if (newScale && newScale > 0)
|
|
59
|
+
transform.scale = newScale;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Nothing to do
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
transform.scale = scale;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (rotDeg)
|
|
70
|
+
transform.rotDeg = rotDeg;
|
|
71
|
+
this.transforms.push(transform);
|
|
72
|
+
this.addElement(new HuatuDrawing(this.symbol, undefined, transform, this.alignCenter));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the 2D generation of the Symbol Array
|
|
77
|
+
* @param countX The count in X-axis
|
|
78
|
+
* @param countY The Count in Y-axis
|
|
79
|
+
* @param dx Interval in X-axis
|
|
80
|
+
* @param dy Interval in Y-axis
|
|
81
|
+
* @param scale Scale factor of the symbol, it could be a number or a func like Sxy = f(ix, iy), where ix and iy
|
|
82
|
+
* is the growing index in X- and Y-axis
|
|
83
|
+
*/
|
|
84
|
+
config2D(startPoint, countX, countY, dx, dy, scale, rotDeg) {
|
|
85
|
+
this.startPoint = startPoint;
|
|
86
|
+
// Clear element buffer at first
|
|
87
|
+
this.elements = [];
|
|
88
|
+
this.arrayMethod = "2d";
|
|
89
|
+
countX = Math.floor(countX);
|
|
90
|
+
countY = Math.floor(countY);
|
|
91
|
+
this.countX = countX >= 1 ? countX : 1;
|
|
92
|
+
this.countY = countY >= 1 ? countY : 1;
|
|
93
|
+
this.deltaX = dx ?? 0;
|
|
94
|
+
this.deltaY = dy ?? 0;
|
|
95
|
+
this.count = this.countX * this.countY;
|
|
96
|
+
if (scale && typeof scale === "number") {
|
|
97
|
+
scale = scale > 0 ? scale : undefined; // non-positive scale will be discarded
|
|
98
|
+
}
|
|
99
|
+
for (let iX = 0; iX < this.countX; iX++) {
|
|
100
|
+
for (let iY = 0; iY < this.countY; iY++) {
|
|
101
|
+
const transform = {
|
|
102
|
+
x: this.startPoint.x + iX * this.deltaX,
|
|
103
|
+
y: this.startPoint.y + iY * this.deltaY,
|
|
104
|
+
};
|
|
105
|
+
if (scale) {
|
|
106
|
+
if (typeof scale === "function") {
|
|
107
|
+
try {
|
|
108
|
+
let newScale = scale(iX, iY);
|
|
109
|
+
if (newScale && newScale > 0)
|
|
110
|
+
transform.scale = newScale;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// Nothing to do
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
transform.scale = scale;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (rotDeg)
|
|
121
|
+
transform.rotDeg = rotDeg;
|
|
122
|
+
this.transforms.push(transform);
|
|
123
|
+
this.addElement(new HuatuDrawing(this.symbol, undefined, transform, this.alignCenter));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the polar method of arraying
|
|
129
|
+
* @param count How many symbols should be arrayed
|
|
130
|
+
* @param center The center to make the Array, i.e. the center of rotating
|
|
131
|
+
* @param radius How far should be the symbol located from the center, it could be a function of rotating of Angle,
|
|
132
|
+
* e.g. R = f(theta), here, the unit of theta is Radian(not degree), to make it convenient to import
|
|
133
|
+
* existing math curves which does this way
|
|
134
|
+
* @param scale How large will the symbol be scaled to, it could be a function of radius
|
|
135
|
+
* @param startDeg The start rotating angle, in degree
|
|
136
|
+
* @param deltaDeg The Interval of the rotating angle
|
|
137
|
+
* @param symbolNotRotate A switch to make sure if the symbol should stay not rotated
|
|
138
|
+
*/
|
|
139
|
+
configPolar(count, center, radius, scale, // The Scale parameter, if could be the function of Radius
|
|
140
|
+
startDeg, deltaDeg, symbolNotRotate) {
|
|
141
|
+
// Clear element buffer at first
|
|
142
|
+
this.elements = [];
|
|
143
|
+
count = Math.floor(count);
|
|
144
|
+
this.count = count >= 1 ? count : 1;
|
|
145
|
+
this.center = center;
|
|
146
|
+
this.startDeg = startDeg ?? 0;
|
|
147
|
+
this.deltaDeg = deltaDeg ?? 30;
|
|
148
|
+
let constantRadius;
|
|
149
|
+
if (typeof radius === "number") {
|
|
150
|
+
constantRadius = Math.abs(radius);
|
|
151
|
+
}
|
|
152
|
+
if (scale && typeof scale === "number") {
|
|
153
|
+
scale = scale > 0 ? scale : undefined; // non-positive scale will be discarded
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < this.count; i++) {
|
|
156
|
+
const deg = this.startDeg + i * this.deltaDeg;
|
|
157
|
+
const ang = AngDeg.deg2Ang(deg); // Here is the conversion, please notice
|
|
158
|
+
// Default radius will be zero, i.e. if no radius specified, all the symbols will be placed at 'Center'
|
|
159
|
+
let r = 0;
|
|
160
|
+
if (constantRadius)
|
|
161
|
+
r = constantRadius;
|
|
162
|
+
else if (typeof radius === "function") {
|
|
163
|
+
try {
|
|
164
|
+
r = radius(ang); // Use the angle in Radian
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
// Nothing to do
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const loc = Vector.fromPoint(this.center).moveByLenAndAng(r, ang);
|
|
171
|
+
const transform = {
|
|
172
|
+
x: loc.x,
|
|
173
|
+
y: loc.y
|
|
174
|
+
};
|
|
175
|
+
if (!symbolNotRotate) {
|
|
176
|
+
transform.rotDeg = deg;
|
|
177
|
+
}
|
|
178
|
+
if (scale) {
|
|
179
|
+
if (typeof scale === "function") {
|
|
180
|
+
try {
|
|
181
|
+
let newScale = scale(r); // Scale is a func of Radius, please note
|
|
182
|
+
if (newScale && newScale > 0)
|
|
183
|
+
transform.scale = newScale;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
// Nothing to do
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
transform.scale = scale;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.transforms.push(transform);
|
|
194
|
+
this.addElement(new HuatuDrawing(this.symbol, undefined, transform, this.alignCenter));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
static optsSymbolArray = {
|
|
198
|
+
keys: [
|
|
199
|
+
["symbol", "sym"],
|
|
200
|
+
["startPoint", "start", "from"],
|
|
201
|
+
["desc"],
|
|
202
|
+
["method", "arrayMethod"],
|
|
203
|
+
["deltaX", "dx"],
|
|
204
|
+
["deltaY", "dy"],
|
|
205
|
+
["count"],
|
|
206
|
+
["countX"],
|
|
207
|
+
["countY"],
|
|
208
|
+
["center", "centerPoint", "at"],
|
|
209
|
+
["rotDeg", "rot", "rotate"],
|
|
210
|
+
["alignCenter"],
|
|
211
|
+
["radius", "r"],
|
|
212
|
+
["scale"],
|
|
213
|
+
["notRotate", "noRotate", "noRot"],
|
|
214
|
+
["startDeg", "initialDeg", "iniDeg"],
|
|
215
|
+
["deltaDeg", "degDelta", "degD", "dDeg"],
|
|
216
|
+
],
|
|
217
|
+
ints: [
|
|
218
|
+
"count",
|
|
219
|
+
"countX",
|
|
220
|
+
"countY",
|
|
221
|
+
],
|
|
222
|
+
numbers: [],
|
|
223
|
+
booleans: [
|
|
224
|
+
"alignCenter",
|
|
225
|
+
"notRotate", "noRotate", "noRot"
|
|
226
|
+
],
|
|
227
|
+
allowAppend: [],
|
|
228
|
+
qualifiers: {
|
|
229
|
+
"method": HuatuSymbolArray.METHODS,
|
|
230
|
+
},
|
|
231
|
+
blindGuess: {
|
|
232
|
+
"method": HuatuSymbolArray.METHODS,
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { SvgPoint } from "../svg/SvgPoint.js";
|
|
2
|
+
import { WtMath } from "../math/WtMath.js";
|
|
3
|
+
export class HuatuTransform {
|
|
4
|
+
x;
|
|
5
|
+
y;
|
|
6
|
+
rotCx;
|
|
7
|
+
rotCy;
|
|
8
|
+
rotDeg;
|
|
9
|
+
scale;
|
|
10
|
+
constructor(x, y, rotCx, rotCy, rotDeg, scale) {
|
|
11
|
+
this.x = x;
|
|
12
|
+
this.y = y;
|
|
13
|
+
this.rotCx = rotCx;
|
|
14
|
+
this.rotCy = rotCy;
|
|
15
|
+
this.rotDeg = rotDeg;
|
|
16
|
+
this.scale = scale;
|
|
17
|
+
}
|
|
18
|
+
static addMove(arr, move) {
|
|
19
|
+
if (!WtMath.isZero(move.x) || !WtMath.isZero(move.y)) {
|
|
20
|
+
arr.push(`translate(${SvgPoint.toCoors(move)})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
static addRotate(arr, rotDeg, rotCenter) {
|
|
24
|
+
if (!WtMath.isZero(rotDeg)) {
|
|
25
|
+
const vals = [rotDeg];
|
|
26
|
+
if (rotCenter) {
|
|
27
|
+
vals.push(rotCenter.x, rotCenter.y);
|
|
28
|
+
}
|
|
29
|
+
const valStrs = vals.map(x => `${x}`).join(",");
|
|
30
|
+
arr.push(`rotate(${valStrs})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
static addScale(arr, scale, scaleCenter) {
|
|
34
|
+
if (!WtMath.isEqual(scale, 1)) {
|
|
35
|
+
if (scaleCenter) {
|
|
36
|
+
const dx = scaleCenter.x * (1 - scale), dy = scaleCenter.y * (1 - scale);
|
|
37
|
+
arr.push(`translate(${dx},${dy})`);
|
|
38
|
+
}
|
|
39
|
+
arr.push(`scale(${scale})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
static buildTransformAttrsForBlockAndPath(transform) {
|
|
43
|
+
if (!transform)
|
|
44
|
+
return [];
|
|
45
|
+
const move = (transform.x || transform.y)
|
|
46
|
+
? new SvgPoint(transform.x ?? 0, transform.y ?? 0)
|
|
47
|
+
: undefined;
|
|
48
|
+
const attrs = [];
|
|
49
|
+
if (transform.rotDeg) {
|
|
50
|
+
const rotCenter = new SvgPoint(0, 0);
|
|
51
|
+
if (transform.rotCx)
|
|
52
|
+
rotCenter.x = transform.rotCx;
|
|
53
|
+
if (transform.rotCy)
|
|
54
|
+
rotCenter.y = transform.rotCy;
|
|
55
|
+
const scale = HuatuTransform.judgeScale(transform);
|
|
56
|
+
// Rotate at
|
|
57
|
+
HuatuTransform.addRotate(attrs, transform.rotDeg, rotCenter);
|
|
58
|
+
// Scale at
|
|
59
|
+
HuatuTransform.addScale(attrs, scale, rotCenter);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
if (transform.scale) {
|
|
63
|
+
const scale = HuatuTransform.judgeScale(transform);
|
|
64
|
+
let scaleCenter = (transform.rotCx || transform.rotCy)
|
|
65
|
+
? new SvgPoint(transform.rotCx ?? 0, transform.rotCy ?? 0)
|
|
66
|
+
: undefined;
|
|
67
|
+
HuatuTransform.addScale(attrs, scale, scaleCenter);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// First move action will be placed as last
|
|
71
|
+
if (move) {
|
|
72
|
+
HuatuTransform.addMove(attrs, move);
|
|
73
|
+
}
|
|
74
|
+
return attrs;
|
|
75
|
+
}
|
|
76
|
+
static buildTransformStrForBlockAndPath(transform) {
|
|
77
|
+
if (!transform)
|
|
78
|
+
return "";
|
|
79
|
+
const attrs = HuatuTransform.buildTransformAttrsForBlockAndPath(transform);
|
|
80
|
+
if (attrs.length < 1)
|
|
81
|
+
return "";
|
|
82
|
+
return `transform="${attrs.join(" ")}"`;
|
|
83
|
+
}
|
|
84
|
+
static buildTransformAttrsForSymbol(transform) {
|
|
85
|
+
if (!transform)
|
|
86
|
+
return [];
|
|
87
|
+
const move = (transform.x || transform.y)
|
|
88
|
+
? new SvgPoint(transform.x ?? 0, transform.y ?? 0)
|
|
89
|
+
: undefined;
|
|
90
|
+
const attrs = [];
|
|
91
|
+
if (transform.rotDeg) {
|
|
92
|
+
// If the rotation center is not given, then use the point of move
|
|
93
|
+
// If both not given, then leave no translate action
|
|
94
|
+
const rotCenter = transform.rotCx || transform.rotCy
|
|
95
|
+
? new SvgPoint(transform.rotCx ?? 0, transform.rotCy ?? 0)
|
|
96
|
+
: move;
|
|
97
|
+
if (rotCenter)
|
|
98
|
+
HuatuTransform.addMove(attrs, rotCenter);
|
|
99
|
+
HuatuTransform.addRotate(attrs, transform.rotDeg);
|
|
100
|
+
}
|
|
101
|
+
else if (move) {
|
|
102
|
+
HuatuTransform.addMove(attrs, move);
|
|
103
|
+
}
|
|
104
|
+
return attrs;
|
|
105
|
+
}
|
|
106
|
+
static judgeScale(transform) {
|
|
107
|
+
if (!transform)
|
|
108
|
+
return 1;
|
|
109
|
+
if (!transform.scale || transform.scale <= 0)
|
|
110
|
+
return 1;
|
|
111
|
+
return transform.scale;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class HuatuVar {
|
|
2
|
+
name;
|
|
3
|
+
value;
|
|
4
|
+
howGet;
|
|
5
|
+
constructor(name, value, howGet) {
|
|
6
|
+
this.howGet = howGet ?? "direct";
|
|
7
|
+
this.name = name;
|
|
8
|
+
this.value = value;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return the value of the variable, return 0 if not set.
|
|
12
|
+
* @returns the value of this variable
|
|
13
|
+
*/
|
|
14
|
+
getValue() {
|
|
15
|
+
return this.value ?? 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { IndentParsingStack } from "./IndentParsingStack.js";
|
|
2
|
+
export class HuatuYml {
|
|
3
|
+
static parseYml(yml) {
|
|
4
|
+
// Prepare error handling
|
|
5
|
+
let hasError = false;
|
|
6
|
+
// Prepare the result
|
|
7
|
+
const res = {
|
|
8
|
+
succeeded: false,
|
|
9
|
+
errorMessages: []
|
|
10
|
+
};
|
|
11
|
+
// The regex to match a "Real" comment, conditions are:
|
|
12
|
+
// - The Comment part starts with a '#' sign
|
|
13
|
+
// - The # sign is not followed by a ':', because this will escape the '#' char to be used in label text
|
|
14
|
+
// - The # sign is not followed by 3/6/8 color (hex) codes(a-z, 0-9), because this will build some color
|
|
15
|
+
const YML_COMMENT_REGEX = /#(?!(:|([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})\b))(.*)$/ig;
|
|
16
|
+
const REGEX_MATCH_LIST = /^(\s*)-(\s.*|$)/i;
|
|
17
|
+
const REGEX_MATCH_KEY_VALUE = /^(\s*)(\$?[a-z][a-z0-9_]*)\s*:\s?(.*)$/i;
|
|
18
|
+
const parseKeyVal = (line) => {
|
|
19
|
+
const match = REGEX_MATCH_KEY_VALUE.exec(line);
|
|
20
|
+
if (match) {
|
|
21
|
+
return {
|
|
22
|
+
indentLevel: Math.floor(match[1].length / 2),
|
|
23
|
+
key: match[2],
|
|
24
|
+
value: match[3].trim(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
let indentLevel = Math.floor(/\s*/.exec(line)[0].length / 2);
|
|
29
|
+
return {
|
|
30
|
+
indentLevel: indentLevel,
|
|
31
|
+
key: undefined,
|
|
32
|
+
value: line.trim(), // If line is not empty, then prepend a space
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
// Process the input
|
|
37
|
+
const ymlLines = yml.replace(/\r/ig, "").split('\n');
|
|
38
|
+
const lines = ymlLines
|
|
39
|
+
.map((l, i) => {
|
|
40
|
+
const lClean = l.replace(/#:/g, "_^;_;^_") // Backup the '#' char
|
|
41
|
+
.replace(YML_COMMENT_REGEX, "") // Remove comment
|
|
42
|
+
.replace(/_\^;_;\^_/g, "#") // Restore # char
|
|
43
|
+
.trimEnd() // Remove trailing spaces
|
|
44
|
+
.replace(/\t/g, " "); // Replace tab key to two spaces
|
|
45
|
+
return lClean
|
|
46
|
+
? {
|
|
47
|
+
value: lClean,
|
|
48
|
+
lineNumber: i + 1
|
|
49
|
+
}
|
|
50
|
+
: undefined;
|
|
51
|
+
})
|
|
52
|
+
.filter(l => !!l)
|
|
53
|
+
.map(l => {
|
|
54
|
+
let lineInfo;
|
|
55
|
+
const tryAsList = l.value.match(REGEX_MATCH_LIST);
|
|
56
|
+
if (tryAsList) {
|
|
57
|
+
const res = parseKeyVal(tryAsList[2].trim());
|
|
58
|
+
lineInfo = {
|
|
59
|
+
lineNumber: l.lineNumber,
|
|
60
|
+
indentLevel: Math.floor(tryAsList[1].length / 2),
|
|
61
|
+
isListItem: true,
|
|
62
|
+
key: res.key,
|
|
63
|
+
value: res.value.trim(),
|
|
64
|
+
isPureValue: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const res = parseKeyVal(l.value);
|
|
69
|
+
lineInfo = {
|
|
70
|
+
lineNumber: l.lineNumber,
|
|
71
|
+
indentLevel: res.indentLevel,
|
|
72
|
+
isListItem: false,
|
|
73
|
+
key: res.key,
|
|
74
|
+
value: res.value,
|
|
75
|
+
isPureValue: !res.key
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return lineInfo;
|
|
79
|
+
})
|
|
80
|
+
.filter(l => !!l);
|
|
81
|
+
// If has error, then skip the steps following
|
|
82
|
+
if (hasError)
|
|
83
|
+
return res;
|
|
84
|
+
if (lines.length < 1) {
|
|
85
|
+
hasError = true;
|
|
86
|
+
res.errorMessages.push({
|
|
87
|
+
lineNumber: 0,
|
|
88
|
+
message: 'No valid line ever input!'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (lines[0].isPureValue) {
|
|
92
|
+
hasError = true;
|
|
93
|
+
res.errorMessages.push({
|
|
94
|
+
lineNumber: lines[0].lineNumber,
|
|
95
|
+
message: "First line mustn't be pure value line!"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (hasError)
|
|
99
|
+
return res;
|
|
100
|
+
// Append pure-value lines into previous
|
|
101
|
+
const realLines = [lines[0]]; // Initialize with the first line
|
|
102
|
+
for (let i = 1; i < lines.length; i++) {
|
|
103
|
+
const line = lines[i];
|
|
104
|
+
if (line.isPureValue && !line.isListItem) {
|
|
105
|
+
if (line.value) {
|
|
106
|
+
const lastLine = realLines[realLines.length - 1];
|
|
107
|
+
if (line.indentLevel > lastLine.indentLevel) {
|
|
108
|
+
lastLine.value = lastLine.value ? `${lastLine.value} ${line.value}` : line.value;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
hasError = true;
|
|
112
|
+
res.errorMessages.push({
|
|
113
|
+
lineNumber: line.lineNumber,
|
|
114
|
+
message: "Continuing text should have more indent than its parent."
|
|
115
|
+
});
|
|
116
|
+
return res;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
realLines.push(line);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Parse the YML lines into Object
|
|
125
|
+
const indentLevelMin = Math.min(...realLines.map(l => l.indentLevel));
|
|
126
|
+
const indentLevelMax = Math.max(...realLines.map(l => l.indentLevel));
|
|
127
|
+
const indentLevelCount = indentLevelMax - indentLevelMin + 1 + 1;
|
|
128
|
+
realLines.forEach(l => {
|
|
129
|
+
l.indentLevel -= indentLevelMin;
|
|
130
|
+
});
|
|
131
|
+
const stacks = [];
|
|
132
|
+
for (let i = 0; i < indentLevelCount; i++) {
|
|
133
|
+
stacks.push(new IndentParsingStack());
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* This function is used to make the object composed by following sub-stacks
|
|
137
|
+
* for the current stack that is going to close current open item.
|
|
138
|
+
*/
|
|
139
|
+
const buildObjOnClose = (indentLevel) => {
|
|
140
|
+
let objOnClose = undefined;
|
|
141
|
+
for (let j = indentLevelCount - 1; j > indentLevel; j--) {
|
|
142
|
+
if (stacks[j].inUse) {
|
|
143
|
+
stacks[j].close(objOnClose);
|
|
144
|
+
objOnClose = stacks[j].buildObj();
|
|
145
|
+
stacks[j].stop();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return objOnClose;
|
|
149
|
+
};
|
|
150
|
+
for (let i = 0; i < realLines.length; i++) {
|
|
151
|
+
const line = realLines[i];
|
|
152
|
+
const stack = stacks[line.indentLevel];
|
|
153
|
+
// Check if the line has invalid indent, i.e. over-indented, or less-indented
|
|
154
|
+
let indentError = "";
|
|
155
|
+
if (line.indentLevel > 0) {
|
|
156
|
+
for (let j = line.indentLevel - 1; j >= 0; j--) {
|
|
157
|
+
if (stacks[j].inUse && !stacks[j].isOpen) {
|
|
158
|
+
indentError = "Over Indented";
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!stack.inUse) {
|
|
164
|
+
for (let j = line.indentLevel + 1; j < indentLevelCount; j++) {
|
|
165
|
+
if (stacks[j].inUse) {
|
|
166
|
+
indentError = "Less Indented";
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (indentError) {
|
|
172
|
+
hasError = true;
|
|
173
|
+
res.errorMessages.push({
|
|
174
|
+
lineNumber: line.lineNumber,
|
|
175
|
+
message: indentError,
|
|
176
|
+
});
|
|
177
|
+
return res;
|
|
178
|
+
}
|
|
179
|
+
if (stack.inUse) {
|
|
180
|
+
// Check if coming item has a different item type, list or not
|
|
181
|
+
if ((stack.isList && !line.isListItem) || (!stack.isList && line.isListItem)) {
|
|
182
|
+
hasError = true;
|
|
183
|
+
res.errorMessages.push({
|
|
184
|
+
lineNumber: line.lineNumber,
|
|
185
|
+
message: "You cannot mix list and/or dictionary definitions."
|
|
186
|
+
});
|
|
187
|
+
return res;
|
|
188
|
+
}
|
|
189
|
+
if (stack.isOpen) { // Let's close all the sub and sub level stacks
|
|
190
|
+
stack.close(buildObjOnClose(line.indentLevel));
|
|
191
|
+
}
|
|
192
|
+
// Push current item into the stack
|
|
193
|
+
if (line.isListItem) {
|
|
194
|
+
if (line.key) {
|
|
195
|
+
stack.open(); // Make it open
|
|
196
|
+
const stackNext = stacks[line.indentLevel + 1];
|
|
197
|
+
stackNext.start(false, line.key, line.value); // Make next-level stack in use
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
if (line.value)
|
|
201
|
+
stack.add("", line.value);
|
|
202
|
+
else
|
|
203
|
+
stack.open();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
if (line.key) {
|
|
208
|
+
const addRes = stack.add(line.key, line.value);
|
|
209
|
+
if (addRes) {
|
|
210
|
+
hasError = true;
|
|
211
|
+
res.errorMessages.push({
|
|
212
|
+
lineNumber: line.lineNumber,
|
|
213
|
+
message: addRes,
|
|
214
|
+
});
|
|
215
|
+
return res;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else { // Start the stack
|
|
221
|
+
if (line.isListItem) {
|
|
222
|
+
if (line.key) {
|
|
223
|
+
stack.start(true);
|
|
224
|
+
const stackNext = stacks[line.indentLevel + 1];
|
|
225
|
+
stackNext.start(false, line.key, line.value); // Leave the key and value to next-level stack
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
stack.start(true, undefined, line.value); // If no key, then add into list if value presented
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
stack.start(false, line.key, line.value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Close the root stack
|
|
237
|
+
stacks[0].close(buildObjOnClose(0));
|
|
238
|
+
res.rootObj = stacks[0].buildObj();
|
|
239
|
+
res.succeeded = true;
|
|
240
|
+
return res;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A tool function to copy the source object to a new one. With the option of accepted key list and
|
|
244
|
+
* string replacing function.
|
|
245
|
+
* @param srcObj The Object to be processed
|
|
246
|
+
* @param acceptedRootKeys A list of which keys are accepted, if not presented, all keys will be accepted
|
|
247
|
+
* @param replaceFunc A function to replace string occurrences
|
|
248
|
+
* @returns The copied/processed object
|
|
249
|
+
*/
|
|
250
|
+
static copyObj(srcObj, acceptedRootKeys, replaceFunc) {
|
|
251
|
+
const resObj = {};
|
|
252
|
+
for (let k in srcObj) {
|
|
253
|
+
if (acceptedRootKeys && !acceptedRootKeys.includes(k))
|
|
254
|
+
continue;
|
|
255
|
+
const v = srcObj[k];
|
|
256
|
+
if (typeof v === "string") {
|
|
257
|
+
resObj[k] = replaceFunc ? replaceFunc(v) : v;
|
|
258
|
+
}
|
|
259
|
+
else if (Array.isArray(v)) {
|
|
260
|
+
resObj[k] = [
|
|
261
|
+
...v.filter(x => typeof x === "string")
|
|
262
|
+
.map(x => replaceFunc ? replaceFunc(x) : x)
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
else if (typeof v === "object") {
|
|
266
|
+
resObj[k] = HuatuYml.copyObj(v, undefined, replaceFunc);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return resObj;
|
|
270
|
+
}
|
|
271
|
+
}
|
package/huatu/IHuatu.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|