circuitjson-toolkit 1.0.3 → 1.0.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.
Files changed (54) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +21 -2
  3. package/docs/api.md +50 -4
  4. package/docs/model-format.md +21 -3
  5. package/package.json +3 -2
  6. package/spec/library-scope.md +4 -1
  7. package/src/core/CircuitJsonBomBuilder.mjs +143 -0
  8. package/src/core/CircuitJsonDocument.mjs +46 -13
  9. package/src/core/CircuitJsonElementValidator.mjs +990 -0
  10. package/src/core/CircuitJsonIndexer.mjs +773 -4
  11. package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
  12. package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
  13. package/src/core/CircuitJsonParser.mjs +22 -6
  14. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
  15. package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
  16. package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
  17. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
  18. package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
  19. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
  20. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
  21. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
  22. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
  23. package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
  24. package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
  25. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
  26. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
  27. package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
  28. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
  29. package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
  30. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
  31. package/src/core/CircuitJsonUnits.mjs +133 -8
  32. package/src/core/PcbBoundsSelectionModel.mjs +250 -0
  33. package/src/core/PcbCandidateSelectionModel.mjs +77 -0
  34. package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
  35. package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
  36. package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
  37. package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
  38. package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
  39. package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
  40. package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
  41. package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
  42. package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
  43. package/src/core/spice/SpiceSimulationService.mjs +92 -0
  44. package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
  45. package/src/index.mjs +8 -0
  46. package/src/renderers.mjs +29 -0
  47. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
  48. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
  49. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
  50. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
  51. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
  52. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
  53. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
  54. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Renders via-like drilled primitives as SVG shapes.
3
+ */
4
+ export class CircuitJsonPcbViaSvgRenderer {
5
+ /**
6
+ * Renders one via-like primitive.
7
+ * @param {object} primitive Via-like primitive.
8
+ * @param {string} attributes Data attributes for the primitive group.
9
+ * @returns {string}
10
+ */
11
+ static render(primitive, attributes = '') {
12
+ const groupAttributes = attributes ? ' ' + attributes : ''
13
+ return (
14
+ '<g class="pcb-via-group"' +
15
+ groupAttributes +
16
+ '>' +
17
+ CircuitJsonPcbViaSvgRenderer.#shape(
18
+ primitive,
19
+ 'pcb-via',
20
+ primitive.shape || 'circle',
21
+ primitive.width || primitive.diameter,
22
+ primitive.height || primitive.diameter
23
+ ) +
24
+ CircuitJsonPcbViaSvgRenderer.#shape(
25
+ primitive,
26
+ 'pcb-via__hole',
27
+ primitive.holeShape || 'circle',
28
+ primitive.holeWidth || primitive.holeDiameter,
29
+ primitive.holeHeight || primitive.holeDiameter
30
+ ) +
31
+ '</g>'
32
+ )
33
+ }
34
+
35
+ /**
36
+ * Renders one centered SVG shape.
37
+ * @param {object} primitive Via-like primitive.
38
+ * @param {string} baseClass Base CSS class.
39
+ * @param {string} shape Shape name.
40
+ * @param {number} width Shape width.
41
+ * @param {number} height Shape height.
42
+ * @returns {string}
43
+ */
44
+ static #shape(primitive, baseClass, shape, width, height) {
45
+ const normalizedShape = String(shape || 'circle').toLowerCase()
46
+ const className =
47
+ baseClass +
48
+ ' ' +
49
+ baseClass.replace('__', '-') +
50
+ '--' +
51
+ CircuitJsonPcbViaSvgRenderer.#escapeHtml(normalizedShape)
52
+
53
+ if (normalizedShape === 'circle') {
54
+ return (
55
+ '<circle class="' +
56
+ className +
57
+ '" cx="' +
58
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(primitive.x) +
59
+ '" cy="' +
60
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(primitive.y) +
61
+ '" r="' +
62
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(width / 2) +
63
+ '"></circle>'
64
+ )
65
+ }
66
+
67
+ if (
68
+ normalizedShape === 'polygon' &&
69
+ Array.isArray(primitive.points) &&
70
+ primitive.points.length >= 3
71
+ ) {
72
+ return (
73
+ '<polygon class="' +
74
+ className +
75
+ '" points="' +
76
+ CircuitJsonPcbViaSvgRenderer.#escapeHtml(
77
+ CircuitJsonPcbViaSvgRenderer.#pointsAttribute(
78
+ primitive.points
79
+ )
80
+ ) +
81
+ '"></polygon>'
82
+ )
83
+ }
84
+
85
+ return (
86
+ '<rect class="' +
87
+ className +
88
+ '" x="' +
89
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(
90
+ primitive.x - width / 2
91
+ ) +
92
+ '" y="' +
93
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(
94
+ primitive.y - height / 2
95
+ ) +
96
+ '" width="' +
97
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(width) +
98
+ '" height="' +
99
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(height) +
100
+ '" rx="' +
101
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(
102
+ normalizedShape === 'pill' ? Math.min(width, height) / 2 : 0
103
+ ) +
104
+ '"' +
105
+ CircuitJsonPcbViaSvgRenderer.#rotationAttribute(primitive) +
106
+ '></rect>'
107
+ )
108
+ }
109
+
110
+ /**
111
+ * Builds a rotation transform attribute.
112
+ * @param {object} primitive Via-like primitive.
113
+ * @returns {string}
114
+ */
115
+ static #rotationAttribute(primitive) {
116
+ const rotation = Number(primitive.rotation || 0)
117
+ if (!Number.isFinite(rotation) || rotation === 0) return ''
118
+ return (
119
+ ' transform="rotate(' +
120
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(rotation) +
121
+ ' ' +
122
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(primitive.x) +
123
+ ' ' +
124
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(primitive.y) +
125
+ ')"'
126
+ )
127
+ }
128
+
129
+ /**
130
+ * Builds a polygon points attribute.
131
+ * @param {{ x: number, y: number }[]} points Point rows.
132
+ * @returns {string}
133
+ */
134
+ static #pointsAttribute(points) {
135
+ return points
136
+ .map(
137
+ (point) =>
138
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(point.x) +
139
+ ',' +
140
+ CircuitJsonPcbViaSvgRenderer.#formatNumber(point.y)
141
+ )
142
+ .join(' ')
143
+ }
144
+
145
+ /**
146
+ * Formats a number for SVG output.
147
+ * @param {number} value Numeric value.
148
+ * @returns {string}
149
+ */
150
+ static #formatNumber(value) {
151
+ const number = Number(value)
152
+ if (!Number.isFinite(number)) return '0'
153
+ return String(Math.round(number * 1_000_000) / 1_000_000)
154
+ }
155
+
156
+ /**
157
+ * Escapes text for HTML attributes.
158
+ * @param {unknown} value Raw value.
159
+ * @returns {string}
160
+ */
161
+ static #escapeHtml(value) {
162
+ return String(value ?? '')
163
+ .replaceAll('&', '&amp;')
164
+ .replaceAll('"', '&quot;')
165
+ .replaceAll('<', '&lt;')
166
+ .replaceAll('>', '&gt;')
167
+ }
168
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Builds SVG path data for schematic arcs.
3
+ */
4
+ export class CircuitJsonSchematicSvgArcPath {
5
+ /**
6
+ * Builds arc path data from start, mid, and end points.
7
+ * @param {{ x: number, y: number } | null} start Start point.
8
+ * @param {{ x: number, y: number } | null} mid Midpoint on the arc.
9
+ * @param {{ x: number, y: number } | null} end End point.
10
+ * @returns {string}
11
+ */
12
+ static fromThreePoints(start, mid, end) {
13
+ if (!start || !mid || !end) return ''
14
+ const circle = CircuitJsonSchematicSvgArcPath.#circle(start, mid, end)
15
+ if (!circle) {
16
+ return (
17
+ 'M ' +
18
+ CircuitJsonSchematicSvgArcPath.#formatPoint(start) +
19
+ ' L ' +
20
+ CircuitJsonSchematicSvgArcPath.#formatPoint(end)
21
+ )
22
+ }
23
+ const startAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, start)
24
+ const midAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, mid)
25
+ const endAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, end)
26
+ const clockwiseSpan = CircuitJsonSchematicSvgArcPath.#positiveDelta(
27
+ startAngle,
28
+ endAngle
29
+ )
30
+ const clockwiseMid = CircuitJsonSchematicSvgArcPath.#positiveDelta(
31
+ startAngle,
32
+ midAngle
33
+ )
34
+ const sweep = clockwiseMid <= clockwiseSpan + 0.000001 ? 1 : 0
35
+ const span =
36
+ sweep === 1
37
+ ? clockwiseSpan
38
+ : CircuitJsonSchematicSvgArcPath.#positiveDelta(
39
+ endAngle,
40
+ startAngle
41
+ )
42
+ const largeArc = span > Math.PI ? 1 : 0
43
+
44
+ return (
45
+ 'M ' +
46
+ CircuitJsonSchematicSvgArcPath.#formatPoint(start) +
47
+ ' A ' +
48
+ CircuitJsonSchematicSvgArcPath.#formatNumber(circle.radius) +
49
+ ' ' +
50
+ CircuitJsonSchematicSvgArcPath.#formatNumber(circle.radius) +
51
+ ' 0 ' +
52
+ largeArc +
53
+ ' ' +
54
+ sweep +
55
+ ' ' +
56
+ CircuitJsonSchematicSvgArcPath.#formatPoint(end)
57
+ )
58
+ }
59
+
60
+ /**
61
+ * Resolves the circle through three points.
62
+ * @param {{ x: number, y: number }} start Start point.
63
+ * @param {{ x: number, y: number }} mid Midpoint.
64
+ * @param {{ x: number, y: number }} end End point.
65
+ * @returns {{ x: number, y: number, radius: number } | null}
66
+ */
67
+ static #circle(start, mid, end) {
68
+ const denominator =
69
+ 2 *
70
+ (start.x * (mid.y - end.y) +
71
+ mid.x * (end.y - start.y) +
72
+ end.x * (start.y - mid.y))
73
+ if (Math.abs(denominator) < 0.000001) return null
74
+
75
+ const startLength = start.x * start.x + start.y * start.y
76
+ const midLength = mid.x * mid.x + mid.y * mid.y
77
+ const endLength = end.x * end.x + end.y * end.y
78
+ const x =
79
+ (startLength * (mid.y - end.y) +
80
+ midLength * (end.y - start.y) +
81
+ endLength * (start.y - mid.y)) /
82
+ denominator
83
+ const y =
84
+ (startLength * (end.x - mid.x) +
85
+ midLength * (start.x - end.x) +
86
+ endLength * (mid.x - start.x)) /
87
+ denominator
88
+ const radius = Math.hypot(start.x - x, start.y - y)
89
+
90
+ return Number.isFinite(radius) ? { x, y, radius } : null
91
+ }
92
+
93
+ /**
94
+ * Resolves the angle from a circle center to a point.
95
+ * @param {{ x: number, y: number }} center Circle center.
96
+ * @param {{ x: number, y: number }} point Point on circle.
97
+ * @returns {number}
98
+ */
99
+ static #angle(center, point) {
100
+ return Math.atan2(point.y - center.y, point.x - center.x)
101
+ }
102
+
103
+ /**
104
+ * Resolves the positive angular delta from start to end.
105
+ * @param {number} start Start angle.
106
+ * @param {number} end End angle.
107
+ * @returns {number}
108
+ */
109
+ static #positiveDelta(start, end) {
110
+ const full = Math.PI * 2
111
+ return (((end - start) % full) + full) % full
112
+ }
113
+
114
+ /**
115
+ * Formats one point for SVG path data.
116
+ * @param {{ x: number, y: number }} point Point.
117
+ * @returns {string}
118
+ */
119
+ static #formatPoint(point) {
120
+ return (
121
+ CircuitJsonSchematicSvgArcPath.#formatNumber(point.x) +
122
+ ' ' +
123
+ CircuitJsonSchematicSvgArcPath.#formatNumber(point.y)
124
+ )
125
+ }
126
+
127
+ /**
128
+ * Formats one SVG number.
129
+ * @param {number} value Number value.
130
+ * @returns {string}
131
+ */
132
+ static #formatNumber(value) {
133
+ const number = Number(value)
134
+ const integer = Math.round(number)
135
+ if (Math.abs(number - integer) < 0.000001) return String(integer)
136
+ return Number(number.toFixed(6)).toString()
137
+ }
138
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Resolves schematic port display metadata from linked source ports.
3
+ */
4
+ export class CircuitJsonSchematicSvgPortMetadata {
5
+ /**
6
+ * Builds source port lookup rows.
7
+ * @param {object[]} sourcePorts Source port rows.
8
+ * @returns {Map<string, object>}
9
+ */
10
+ static sourcePorts(sourcePorts) {
11
+ return new Map(
12
+ sourcePorts
13
+ .map((element) => [
14
+ String(element.source_port_id || '').trim(),
15
+ element
16
+ ])
17
+ .filter(([sourcePortId]) => sourcePortId)
18
+ )
19
+ }
20
+
21
+ /**
22
+ * Builds the schematic port group attributes.
23
+ * @param {object} element Schematic port row.
24
+ * @param {object | null} sourcePort Linked source port row.
25
+ * @returns {string}
26
+ */
27
+ static attributes(element, sourcePort) {
28
+ const sourcePortId = String(element.source_port_id || '').trim()
29
+ const pinNumber = CircuitJsonSchematicSvgPortMetadata.#firstText(
30
+ element.pin_number,
31
+ sourcePort?.pin_number
32
+ )
33
+ const direction = String(
34
+ element.facing_direction || element.direction || ''
35
+ ).trim()
36
+ const attributes = [
37
+ 'class="schematic-port"',
38
+ 'data-schematic-port-id="' +
39
+ CircuitJsonSchematicSvgPortMetadata.#escapeHtml(
40
+ element.schematic_port_id || ''
41
+ ) +
42
+ '"',
43
+ 'data-facing-direction="' +
44
+ CircuitJsonSchematicSvgPortMetadata.#escapeHtml(direction) +
45
+ '"'
46
+ ]
47
+
48
+ if (sourcePortId) {
49
+ attributes.push(
50
+ 'data-source-port-id="' +
51
+ CircuitJsonSchematicSvgPortMetadata.#escapeHtml(
52
+ sourcePortId
53
+ ) +
54
+ '"'
55
+ )
56
+ }
57
+ if (pinNumber) {
58
+ attributes.push(
59
+ 'data-pin-number="' +
60
+ CircuitJsonSchematicSvgPortMetadata.#escapeHtml(pinNumber) +
61
+ '"'
62
+ )
63
+ }
64
+
65
+ return attributes.join(' ')
66
+ }
67
+
68
+ /**
69
+ * Resolves a visible schematic port label.
70
+ * @param {object} element Schematic port row.
71
+ * @param {object | null} sourcePort Linked source port row.
72
+ * @returns {string}
73
+ */
74
+ static label(element, sourcePort) {
75
+ return CircuitJsonSchematicSvgPortMetadata.#firstText(
76
+ element.name,
77
+ element.label,
78
+ sourcePort?.name,
79
+ sourcePort?.label,
80
+ sourcePort?.pin_label,
81
+ element.pin_number,
82
+ sourcePort?.pin_number,
83
+ ...(Array.isArray(sourcePort?.port_hints)
84
+ ? sourcePort.port_hints
85
+ : [])
86
+ )
87
+ }
88
+
89
+ /**
90
+ * Resolves the first non-empty display text value.
91
+ * @param {...unknown} values Candidate values.
92
+ * @returns {string}
93
+ */
94
+ static #firstText(...values) {
95
+ return (
96
+ values
97
+ .map((value) => String(value ?? '').trim())
98
+ .find((value) => value) || ''
99
+ )
100
+ }
101
+
102
+ /**
103
+ * Escapes markup text.
104
+ * @param {unknown} value Raw value.
105
+ * @returns {string}
106
+ */
107
+ static #escapeHtml(value) {
108
+ return String(value ?? '')
109
+ .replaceAll('&', '&amp;')
110
+ .replaceAll('<', '&lt;')
111
+ .replaceAll('>', '&gt;')
112
+ .replaceAll('"', '&quot;')
113
+ }
114
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Builds SVG drawing attributes for schematic primitive rows.
3
+ */
4
+ export class CircuitJsonSchematicSvgPrimitiveAttributes {
5
+ /**
6
+ * Builds optional SVG attributes from common drawing style fields.
7
+ * @param {object} element CircuitJSON element row.
8
+ * @param {{ fill?: boolean }} [options] Attribute options.
9
+ * @returns {string}
10
+ */
11
+ static attributes(element, options = {}) {
12
+ const attributes = []
13
+ const strokeWidth = CircuitJsonSchematicSvgPrimitiveAttributes.#number(
14
+ element?.stroke_width ?? element?.strokeWidth
15
+ )
16
+ const strokeColor = CircuitJsonSchematicSvgPrimitiveAttributes.#text(
17
+ element?.stroke_color ?? element?.strokeColor ?? element?.color
18
+ )
19
+ const fillColor = CircuitJsonSchematicSvgPrimitiveAttributes.#text(
20
+ element?.fill_color ?? element?.fillColor
21
+ )
22
+ const dashArray =
23
+ CircuitJsonSchematicSvgPrimitiveAttributes.#dashArray(element)
24
+
25
+ if (strokeWidth !== null) {
26
+ attributes.push(
27
+ 'stroke-width="' +
28
+ CircuitJsonSchematicSvgPrimitiveAttributes.#formatNumber(
29
+ strokeWidth
30
+ ) +
31
+ '"'
32
+ )
33
+ }
34
+ if (strokeColor) {
35
+ attributes.push(
36
+ 'stroke="' +
37
+ CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
38
+ strokeColor
39
+ ) +
40
+ '"'
41
+ )
42
+ }
43
+ if (options.fill !== false && fillColor) {
44
+ attributes.push(
45
+ 'fill="' +
46
+ CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
47
+ fillColor
48
+ ) +
49
+ '"'
50
+ )
51
+ }
52
+ if (dashArray) {
53
+ attributes.push(
54
+ 'stroke-dasharray="' +
55
+ CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
56
+ dashArray
57
+ ) +
58
+ '"'
59
+ )
60
+ }
61
+
62
+ return attributes.length ? ' ' + attributes.join(' ') : ''
63
+ }
64
+
65
+ /**
66
+ * Resolves a finite number.
67
+ * @param {unknown} value Number candidate.
68
+ * @returns {number | null}
69
+ */
70
+ static #number(value) {
71
+ const number = Number(value)
72
+ return Number.isFinite(number) ? number : null
73
+ }
74
+
75
+ /**
76
+ * Resolves non-empty text.
77
+ * @param {unknown} value Text candidate.
78
+ * @returns {string}
79
+ */
80
+ static #text(value) {
81
+ return String(value ?? '').trim()
82
+ }
83
+
84
+ /**
85
+ * Resolves a stroke dash array.
86
+ * @param {object} element CircuitJSON element row.
87
+ * @returns {string}
88
+ */
89
+ static #dashArray(element) {
90
+ const value =
91
+ element?.stroke_dasharray ??
92
+ element?.strokeDasharray ??
93
+ element?.dash_pattern ??
94
+ element?.dashPattern
95
+ const values = Array.isArray(value) ? value : [value]
96
+ const parts = values
97
+ .map((entry) =>
98
+ CircuitJsonSchematicSvgPrimitiveAttributes.#number(entry)
99
+ )
100
+ .filter((entry) => entry !== null)
101
+ .map((entry) =>
102
+ CircuitJsonSchematicSvgPrimitiveAttributes.#formatNumber(entry)
103
+ )
104
+
105
+ if (parts.length) return parts.join(' ')
106
+ return element?.is_dashed ? '0.4 0.25' : ''
107
+ }
108
+
109
+ /**
110
+ * Formats one SVG number.
111
+ * @param {number} value Number value.
112
+ * @returns {string}
113
+ */
114
+ static #formatNumber(value) {
115
+ return Number(Number(value).toFixed(6)).toString()
116
+ }
117
+
118
+ /**
119
+ * Escapes markup text.
120
+ * @param {unknown} value Raw value.
121
+ * @returns {string}
122
+ */
123
+ static #escapeHtml(value) {
124
+ return String(value ?? '')
125
+ .replaceAll('&', '&amp;')
126
+ .replaceAll('<', '&lt;')
127
+ .replaceAll('>', '&gt;')
128
+ .replaceAll('"', '&quot;')
129
+ }
130
+ }