@mcpher/gas-fakes 2.5.3 → 2.5.5
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/.claspignore +1 -1
- package/README.md +36 -3
- package/exgcp.sh +63 -0
- package/package.json +1 -1
- package/src/services/content/contentservice.js +3 -2
- package/src/services/documentapp/appenderhelpers.js +8 -3
- package/src/services/documentapp/elementhelpers.js +148 -7
- package/src/services/documentapp/elementoptions.js +40 -0
- package/src/services/documentapp/elements.js +7 -0
- package/src/services/documentapp/fakebookmark.js +1 -3
- package/src/services/documentapp/fakecontainerelement.js +188 -0
- package/src/services/documentapp/fakedate.js +92 -0
- package/src/services/documentapp/fakedocument.js +51 -0
- package/src/services/documentapp/fakedocumenttab.js +9 -25
- package/src/services/documentapp/fakeelement.js +453 -90
- package/src/services/documentapp/fakeequation.js +28 -0
- package/src/services/documentapp/fakeequationfunction.js +37 -0
- package/src/services/documentapp/fakeequationfunctionargumentseparator.js +28 -0
- package/src/services/documentapp/fakeequationsymbol.js +37 -0
- package/src/services/documentapp/fakefootersection.js +64 -1
- package/src/services/documentapp/fakeheadersection.js +64 -0
- package/src/services/documentapp/fakeperson.js +67 -0
- package/src/services/documentapp/fakerangeelement.js +27 -1
- package/src/services/documentapp/fakerichlink.js +79 -0
- package/src/services/documentapp/fakesectionelement.js +51 -12
- package/src/services/documentapp/faketablecell.js +98 -0
- package/src/services/documentapp/nrhelpers.js +2 -1
- package/src/services/documentapp/shadowdocument.js +19 -2
- package/src/services/enums/contentenums.js +1 -3
- package/src/services/enums/scriptenums.js +31 -4
- package/src/services/enums/xmlenums.js +14 -0
- package/src/services/scriptapp/app.js +14 -7
- package/src/services/scriptapp/fakeauthorizationinfo.js +4 -4
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +87 -12
- package/src/services/spreadsheetapp/fakespreadsheet.js +360 -62
- package/src/services/spreadsheetapp/fakespreadsheettheme.js +53 -0
- package/src/services/urlfetchapp/app.js +216 -175
- package/src/services/xmlservice/app.js +3 -78
- package/src/services/xmlservice/fakeattribute.js +15 -0
- package/src/services/xmlservice/fakecdata.js +40 -0
- package/src/services/xmlservice/fakecomment.js +34 -0
- package/src/services/xmlservice/fakecontent.js +51 -0
- package/src/services/xmlservice/fakedoctype.js +68 -0
- package/src/services/xmlservice/fakedocument.js +110 -13
- package/src/services/xmlservice/fakeelement.js +297 -82
- package/src/services/xmlservice/fakeentityref.js +54 -0
- package/src/services/xmlservice/fakeformat.js +67 -22
- package/src/services/xmlservice/fakeprocessinginstruction.js +44 -0
- package/src/services/xmlservice/faketext.js +39 -0
- package/src/services/xmlservice/fakexmlservice.js +118 -0
- package/src/support/sxfetch.js +60 -0
- package/tools/omlx.env.example +6 -0
- package/tools/omlx_mcp_server.cjs +157 -0
|
@@ -1,217 +1,258 @@
|
|
|
1
1
|
// fake Apps Script UrlFetchApp
|
|
2
2
|
|
|
3
|
-
import { Auth } from '../../support/auth.js'
|
|
4
3
|
import { Syncit } from '../../support/syncit.js'
|
|
5
4
|
import { Proxies } from '../../support/proxies.js'
|
|
6
5
|
|
|
7
|
-
// Note that all async type functions have been converted to synch ro make it Apps Script like
|
|
8
|
-
|
|
9
|
-
|
|
10
6
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @
|
|
7
|
+
* Helper function to fix and normalize HTTP headers.
|
|
8
|
+
* @param {object} response - The raw response object.
|
|
9
|
+
* @returns {object} Normalized headers object.
|
|
14
10
|
*/
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
getResponseCode: () => 500,
|
|
21
|
-
getContentText: () => "UrlFetchApp: No response data available.",
|
|
22
|
-
getHeaders: () => ({}),
|
|
23
|
-
getBlob: () => null,
|
|
24
|
-
getContent: () => []
|
|
25
|
-
}
|
|
11
|
+
const fixHeaders = (response) => {
|
|
12
|
+
// it's a map with a pair of values
|
|
13
|
+
const headers = (response.rawHeaders || [])
|
|
14
|
+
if (headers.length % 2 !== 0) {
|
|
15
|
+
throw `fixHeaders:invalid number of header value pairs - ${headers.length}`
|
|
26
16
|
}
|
|
17
|
+
// split into pairs and return as object
|
|
18
|
+
const mapHeaders = new Map(headers.flatMap((_, i, a) => i % 2 ? [] : [a.slice(i, i + 2)]));
|
|
19
|
+
return Object.fromEntries(mapHeaders)
|
|
20
|
+
}
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Helper function to convert raw response data into a FakeBlob.
|
|
24
|
+
* Assumes Utilities.newBlob is available in the scope.
|
|
25
|
+
* @param {object} response - The raw response object.
|
|
26
|
+
* @param {object} headers - The normalized headers.
|
|
27
|
+
* @returns {object} A FakeBlob representation.
|
|
28
|
+
*/
|
|
29
|
+
const blobify = (response, headers) => {
|
|
30
|
+
if (!headers) return null
|
|
31
|
+
// the name is buried in the Conent-Disposition
|
|
32
|
+
const disp = headers["Content-Disposition"]
|
|
33
|
+
const filename = disp && disp.replace(/.*filename="([^"]*).*/, "$1")
|
|
34
|
+
const name = filename || ""
|
|
35
|
+
const contentType = headers["Content-Type"]?.replace(/([^;]*).*/, "$1").trim()
|
|
36
|
+
const bytes = response.rawBody || response.body || null
|
|
37
|
+
// Assuming Utilities is available globally or imported elsewhere
|
|
38
|
+
return Utilities.newBlob(bytes, contentType, name)
|
|
39
|
+
}
|
|
30
40
|
|
|
31
|
-
// getResponseCode() Integer Get the HTTP status code (200 for OK, etc.) of an HTTP response
|
|
32
|
-
const getResponseCode = () => response.statusCode || response.status || 500
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Represents a fake HTTP response object, mimicking UrlFetchApp's response structure.
|
|
44
|
+
*/
|
|
45
|
+
export class FakeHTTPResponse {
|
|
46
|
+
/**
|
|
47
|
+
* @param {object} response - The raw response data from Syncit.
|
|
48
|
+
*/
|
|
49
|
+
constructor(response) {
|
|
50
|
+
this._response = response
|
|
51
|
+
this._headers = null
|
|
52
|
+
}
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
/**
|
|
55
|
+
* @returns {object} Attribute/value map of headers.
|
|
56
|
+
*/
|
|
57
|
+
getAllHeaders() {
|
|
58
|
+
if (!this._response) return {}
|
|
59
|
+
if (!this._headers) {
|
|
60
|
+
this._headers = fixHeaders(this._response)
|
|
61
|
+
}
|
|
62
|
+
return this._headers
|
|
63
|
+
}
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
/**
|
|
66
|
+
* @returns {number} The HTTP status code.
|
|
67
|
+
*/
|
|
68
|
+
getResponseCode() {
|
|
69
|
+
if (!this._response) return 500
|
|
70
|
+
return this._response.statusCode || this._response.status || 500
|
|
71
|
+
}
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
/**
|
|
74
|
+
* @returns {string} The content of an HTTP response encoded as a string.
|
|
75
|
+
*/
|
|
76
|
+
getContentText() {
|
|
77
|
+
if (!this._response) return "UrlFetchApp: No response data available."
|
|
78
|
+
const headers = this.getAllHeaders()
|
|
79
|
+
const blob = blobify(this._response, headers)
|
|
80
|
+
return blob ? blob.getDataAsString() : ""
|
|
81
|
+
}
|
|
45
82
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (headers % 2) {
|
|
52
|
-
throw `fixHeaders:invalid number of header value pairs - ${headers.length}`
|
|
53
|
-
}
|
|
54
|
-
// split into pairs and return as object
|
|
55
|
-
const mapHeaders = new Map(headers.flatMap((_, i, a) => i % 2 ? [] : [a.slice(i, i + 2)]));
|
|
56
|
-
return Object.fromEntries(mapHeaders)
|
|
83
|
+
/**
|
|
84
|
+
* @returns {object} Attribute/value map of headers.
|
|
85
|
+
*/
|
|
86
|
+
getHeaders() {
|
|
87
|
+
return this.getAllHeaders()
|
|
57
88
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const contentType = headers["Content-Type"]?.replace(/([^;]*).*/, "$1").trim()
|
|
66
|
-
const bytes = response.rawBody || response.body || null
|
|
67
|
-
return Utilities.newBlob(bytes, contentType, name)
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns {ArrayBuffer | Uint8Array} The raw binary content of an HTTP response.
|
|
92
|
+
*/
|
|
93
|
+
getContent() {
|
|
94
|
+
if (!this._response) return []
|
|
95
|
+
return this._response.rawBody || this._response.body
|
|
68
96
|
}
|
|
69
97
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
getAllHeaders,
|
|
78
|
-
getResponseCode,
|
|
79
|
-
getContentText,
|
|
80
|
-
getHeaders,
|
|
81
|
-
getBlob,
|
|
82
|
-
getContent
|
|
98
|
+
/**
|
|
99
|
+
* @returns {object} A FakeBlob representing the content.
|
|
100
|
+
*/
|
|
101
|
+
getBlob() {
|
|
102
|
+
if (!this._response) return null
|
|
103
|
+
const headers = this.getAllHeaders()
|
|
104
|
+
return blobify(this._response, headers)
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Represents a fake UrlFetchApp service, mimicking the global Apps Script service.
|
|
110
|
+
*/
|
|
111
|
+
export class FakeUrlFetchApp {
|
|
112
|
+
/**
|
|
113
|
+
* @param {object} [options] - Configuration options (optional).
|
|
114
|
+
*/
|
|
115
|
+
constructor(options = {}) {
|
|
116
|
+
this.options = options
|
|
117
|
+
}
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Performs a GET request.
|
|
121
|
+
* @param {string} url - The URL to fetch.
|
|
122
|
+
* @param {object} [options] - Request options.
|
|
123
|
+
* @returns {FakeHTTPResponse} The response object.
|
|
124
|
+
*/
|
|
125
|
+
fetch(url, options = {}) {
|
|
126
|
+
// check options for method and provide default
|
|
127
|
+
options.method = options.method || "get"
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
const responseFields = [
|
|
130
|
+
'rawHeaders',
|
|
131
|
+
'statusCode',
|
|
132
|
+
'body',
|
|
133
|
+
'headers',
|
|
134
|
+
'rawBody'
|
|
135
|
+
]
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
137
|
+
const { data } = Syncit.fxFetch(url, options, responseFields)
|
|
138
|
+
return new FakeHTTPResponse(data)
|
|
139
|
+
}
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Performs multiple requests concurrently.
|
|
143
|
+
* @param {Array<object>} requests - Array of request configurations.
|
|
144
|
+
* @returns {Array<FakeHTTPResponse>} Array of response objects.
|
|
145
|
+
*/
|
|
146
|
+
fetchAll(requests) {
|
|
147
|
+
const responseFields = [
|
|
148
|
+
'rawHeaders',
|
|
149
|
+
'statusCode',
|
|
150
|
+
'body',
|
|
151
|
+
'headers',
|
|
152
|
+
'rawBody'
|
|
153
|
+
]
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'body',
|
|
111
|
-
'headers',
|
|
112
|
-
'rawBody'
|
|
113
|
-
]
|
|
155
|
+
const responses = Syncit.fxFetchAll(requests, responseFields)
|
|
156
|
+
return responses.map(({ data }) => new FakeHTTPResponse(data))
|
|
157
|
+
}
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
159
|
+
/**
|
|
160
|
+
* Generates a standardized request object for UrlFetchApp.
|
|
161
|
+
* @param {string} url - The target URL.
|
|
162
|
+
* @param {object} [options] - Request options.
|
|
163
|
+
* @returns {object} The standardized request object.
|
|
164
|
+
*/
|
|
165
|
+
getRequest(url, options = {}) {
|
|
166
|
+
const defaultMethod = 'get';
|
|
167
|
+
const request = {
|
|
168
|
+
url: url,
|
|
169
|
+
method: (options.method || defaultMethod).toLowerCase(),
|
|
170
|
+
headers: options.headers || {},
|
|
171
|
+
};
|
|
118
172
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
method: (options.method || defaultMethod).toLowerCase(),
|
|
124
|
-
headers: options.headers || {},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// Apps Script defaults
|
|
128
|
-
if (options.contentType) {
|
|
129
|
-
request.contentType = options.contentType;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (options.payload) {
|
|
133
|
-
request.payload = options.payload;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (options.useIntranet !== undefined) {
|
|
137
|
-
request.useIntranet = options.useIntranet;
|
|
138
|
-
}
|
|
173
|
+
// Apps Script defaults
|
|
174
|
+
if (options.contentType) {
|
|
175
|
+
request.contentType = options.contentType;
|
|
176
|
+
}
|
|
139
177
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!request.contentType) {
|
|
143
|
-
request.contentType = 'application/x-www-form-urlencoded';
|
|
144
|
-
}
|
|
145
|
-
}
|
|
178
|
+
if (options.payload !== undefined && options.payload !== null) {
|
|
179
|
+
const payload = options.payload;
|
|
146
180
|
|
|
147
|
-
|
|
181
|
+
// 1. Handle Blob payload
|
|
182
|
+
if (typeof payload.getDataAsString === 'function') {
|
|
183
|
+
request.payload = payload.getDataAsString();
|
|
184
|
+
}
|
|
185
|
+
// 2. Handle Byte[] array payload (Array of numbers)
|
|
186
|
+
else if (Array.isArray(payload) && payload.every(item => typeof item === 'number')) {
|
|
187
|
+
// Convert byte array to UTF-8 string
|
|
188
|
+
request.payload = Buffer.from(payload).toString('utf8');
|
|
189
|
+
}
|
|
190
|
+
// 3. Handle plain JavaScript object payload
|
|
191
|
+
else if (typeof payload === 'object' && !Buffer.isBuffer(payload)) {
|
|
192
|
+
// Serialize object to form-encoded string
|
|
193
|
+
const params = new URLSearchParams();
|
|
194
|
+
for (const key in payload) {
|
|
195
|
+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
196
|
+
params.append(key, payload[key]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
request.payload = params.toString();
|
|
200
|
+
|
|
201
|
+
// Set default content type for form data
|
|
202
|
+
if (!request.contentType) {
|
|
203
|
+
request.contentType = 'application/x-www-form-urlencoded';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Default case: pass payload directly (e.g., string, Buffer, etc.)
|
|
207
|
+
else {
|
|
208
|
+
request.payload = payload;
|
|
209
|
+
}
|
|
210
|
+
} else if (options.payload === null) {
|
|
211
|
+
request.payload = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.useIntranet !== undefined) {
|
|
215
|
+
request.useIntranet = options.useIntranet;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Add standard fetch behavior
|
|
219
|
+
if (request.method === 'post' || request.method === 'put' || request.method === 'patch') {
|
|
220
|
+
if (!request.contentType) {
|
|
221
|
+
request.contentType = 'application/x-www-form-urlencoded';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return request;
|
|
226
|
+
}
|
|
148
227
|
}
|
|
149
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Helper function to create and return a new instance of FakeUrlFetchApp.
|
|
231
|
+
* @returns {FakeUrlFetchApp}
|
|
232
|
+
*/
|
|
233
|
+
export const newFakeUrlFetchApp = () => new FakeUrlFetchApp();
|
|
234
|
+
|
|
150
235
|
|
|
151
236
|
// This will eventually hold a proxy for DriveApp
|
|
152
237
|
let _app = null
|
|
153
238
|
|
|
154
239
|
/**
|
|
155
|
-
*
|
|
240
|
+
* Gets the singleton instance of FakeUrlFetchApp.
|
|
241
|
+
* @returns {FakeUrlFetchApp}
|
|
156
242
|
*/
|
|
157
|
-
const
|
|
158
|
-
if
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// if it hasne been intialized yet then do that
|
|
162
|
-
if (!_app) {
|
|
163
|
-
_app = {
|
|
164
|
-
fetch,
|
|
165
|
-
fetchAll,
|
|
166
|
-
getRequest
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// this is the actual driveApp we'll return from the proxy
|
|
170
|
-
return _app
|
|
243
|
+
const getApp = () => {
|
|
244
|
+
// if it hasn't been initialized yet then do that
|
|
245
|
+
if (!_app) {
|
|
246
|
+
_app = newFakeUrlFetchApp()
|
|
171
247
|
}
|
|
248
|
+
// return the actual driveApp we'll return from the proxy
|
|
249
|
+
return _app
|
|
250
|
+
}
|
|
172
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Adds to global space to mimic Apps Script behavior
|
|
254
|
+
*/
|
|
255
|
+
const name = "UrlFetchApp"
|
|
256
|
+
if (typeof globalThis[name] === typeof undefined) {
|
|
173
257
|
Proxies.registerProxy(name, getApp)
|
|
174
|
-
|
|
175
258
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/** got reponse props
|
|
179
|
-
[ '_events', 'object' ],
|
|
180
|
-
[ '_readableState', 'object' ],
|
|
181
|
-
[ '_writableState', 'object' ],
|
|
182
|
-
[ 'allowHalfOpen', 'boolean' ],
|
|
183
|
-
[ '_destroy', 'function' ],
|
|
184
|
-
[ '_maxListeners', 'undefined' ],
|
|
185
|
-
[ '_eventsCount', 'number' ],
|
|
186
|
-
[ 'socket', 'object' ],
|
|
187
|
-
[ 'httpVersionMajor', 'number' ],
|
|
188
|
-
[ 'httpVersionMinor', 'number' ],
|
|
189
|
-
[ 'httpVersion', 'string' ],
|
|
190
|
-
[ 'complete', 'boolean' ],
|
|
191
|
-
[ 'rawHeaders', 'object' ],
|
|
192
|
-
[ 'rawTrailers', 'object' ],
|
|
193
|
-
[ 'joinDuplicateHeaders', 'undefined' ],
|
|
194
|
-
[ 'aborted', 'boolean' ],
|
|
195
|
-
[ 'upgrade', 'boolean' ],
|
|
196
|
-
[ 'url', 'string' ],
|
|
197
|
-
[ 'method', 'object' ],
|
|
198
|
-
[ 'statusCode', 'number' ],
|
|
199
|
-
[ 'statusMessage', 'string' ],
|
|
200
|
-
[ 'client', 'object' ],
|
|
201
|
-
[ '_consuming', 'boolean' ],
|
|
202
|
-
[ '_dumped', 'boolean' ],
|
|
203
|
-
[ 'req', 'object' ],
|
|
204
|
-
[ 'timings', 'object' ],
|
|
205
|
-
[ 'headers', 'object' ],
|
|
206
|
-
[ 'setTimeout', 'function' ],
|
|
207
|
-
[ 'trailers', 'object' ],
|
|
208
|
-
[ 'requestUrl', 'object' ],
|
|
209
|
-
[ 'redirectUrls', 'object' ],
|
|
210
|
-
[ 'request', 'object' ],
|
|
211
|
-
[ 'isFromCache', 'boolean' ],
|
|
212
|
-
[ 'ip', 'string' ],
|
|
213
|
-
[ 'retryCount', 'number' ],
|
|
214
|
-
[ 'ok', 'boolean' ],
|
|
215
|
-
[ 'rawBody', 'object' ],
|
|
216
|
-
[ 'body', 'string' ]
|
|
217
|
-
*/
|
|
@@ -1,81 +1,6 @@
|
|
|
1
1
|
// fake Apps Script XmlService
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { FakeDocument } from './fakedocument.js';
|
|
5
|
-
import { FakeElement } from './fakeelement.js';
|
|
6
|
-
import { FakeNamespace } from './fakenamespace.js';
|
|
7
|
-
import { FakeFormat } from './fakeformat.js';
|
|
2
|
+
import { newFakeXmlService as maker } from './fakexmlservice.js';
|
|
3
|
+
import { lazyLoaderApp } from '../common/lazyloader.js';
|
|
8
4
|
|
|
9
|
-
/**
|
|
10
|
-
* Parses the given XML string and returns a Document object.
|
|
11
|
-
* @param {string} xml The XML string to parse.
|
|
12
|
-
* @return {FakeDocument} The parsed document.
|
|
13
|
-
*/
|
|
14
|
-
const parse = (xml) => {
|
|
15
|
-
const options = {
|
|
16
|
-
ignoreAttributes: false,
|
|
17
|
-
attributeNamePrefix: "@_",
|
|
18
|
-
textNodeName: "#text",
|
|
19
|
-
alwaysCreateTextNode: true,
|
|
20
|
-
removeNSPrefix: false // We need prefixes to map them in FakeElement
|
|
21
|
-
};
|
|
22
|
-
const parser = new XMLParser(options);
|
|
23
|
-
const jsonObj = parser.parse(xml);
|
|
24
|
-
|
|
25
|
-
// The jsonObj will have the root element name as a key
|
|
26
|
-
const rootName = Object.keys(jsonObj).find(key => !key.startsWith('?')); // Ignore processing instructions
|
|
27
|
-
if (!rootName) {
|
|
28
|
-
throw new Error("XmlService: No root element found in XML.");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const rootData = jsonObj[rootName];
|
|
32
|
-
const rootElement = new FakeElement(rootName, rootData);
|
|
33
|
-
return new FakeDocument(rootElement);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Returns a Namespace object with the given prefix and URI.
|
|
38
|
-
* @param {string} prefix The prefix.
|
|
39
|
-
* @param {string} uri The URI.
|
|
40
|
-
* @return {FakeNamespace} The namespace.
|
|
41
|
-
*/
|
|
42
|
-
const getNamespace = (prefix, uri) => {
|
|
43
|
-
return new FakeNamespace(prefix, uri);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Returns a Format object for pretty printing.
|
|
48
|
-
* @return {FakeFormat} The format object.
|
|
49
|
-
*/
|
|
50
|
-
const getPrettyFormat = () => {
|
|
51
|
-
return new FakeFormat({ pretty: true });
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Returns a Format object for raw output.
|
|
56
|
-
* @return {FakeFormat} The format object.
|
|
57
|
-
*/
|
|
58
|
-
const getRawFormat = () => {
|
|
59
|
-
return new FakeFormat({ pretty: false });
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Singleton app object
|
|
63
5
|
let _app = null;
|
|
64
|
-
|
|
65
|
-
const name = "XmlService";
|
|
66
|
-
if (typeof globalThis[name] === typeof undefined) {
|
|
67
|
-
const getApp = () => {
|
|
68
|
-
if (!_app) {
|
|
69
|
-
_app = {
|
|
70
|
-
parse,
|
|
71
|
-
getNamespace,
|
|
72
|
-
getPrettyFormat,
|
|
73
|
-
getRawFormat,
|
|
74
|
-
toString: () => name
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return _app;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
Proxies.registerProxy(name, getApp);
|
|
81
|
-
}
|
|
6
|
+
_app = lazyLoaderApp(_app, 'XmlService', maker);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FakeNamespace } from './fakenamespace.js';
|
|
2
|
+
|
|
3
|
+
export class FakeAttribute {
|
|
4
|
+
constructor(name, value, namespace = null) {
|
|
5
|
+
this._name = name;
|
|
6
|
+
this._value = value;
|
|
7
|
+
this._namespace = namespace || new FakeNamespace("", "");
|
|
8
|
+
}
|
|
9
|
+
getName() { return this._name; }
|
|
10
|
+
getValue() { return this._value; }
|
|
11
|
+
getNamespace() { return this._namespace; }
|
|
12
|
+
setName(name) { this._name = name; return this; }
|
|
13
|
+
setValue(value) { this._value = value; return this; }
|
|
14
|
+
setNamespace(namespace) { this._namespace = namespace; return this; }
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FakeContent } from './fakecontent.js';
|
|
2
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
3
|
+
|
|
4
|
+
export class FakeCdata extends FakeContent {
|
|
5
|
+
constructor(text, parentElement = null) {
|
|
6
|
+
super(Enums.ContentTypes.CDATA);
|
|
7
|
+
this._text = text || '';
|
|
8
|
+
this._parent = parentElement;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
append(text) {
|
|
12
|
+
this._text += text || '';
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getText() {
|
|
17
|
+
return this._text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getValue() {
|
|
21
|
+
return this._text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setText(text) {
|
|
25
|
+
this._text = text || '';
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
detach() {
|
|
30
|
+
return super.detach();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getParentElement() {
|
|
34
|
+
return super.getParentElement();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toString() {
|
|
38
|
+
return `[CDATA: ${this._text}]`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FakeContent } from './fakecontent.js';
|
|
2
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
3
|
+
|
|
4
|
+
export class FakeComment extends FakeContent {
|
|
5
|
+
constructor(text = '') {
|
|
6
|
+
super(Enums.ContentTypes.COMMENT);
|
|
7
|
+
this._text = text;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getText() {
|
|
11
|
+
return this._text;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setText(text) {
|
|
15
|
+
this._text = text || '';
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getValue() {
|
|
20
|
+
return this._text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
detach() {
|
|
24
|
+
return super.detach();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getParentElement() {
|
|
28
|
+
return super.getParentElement();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
toString() {
|
|
32
|
+
return `[Comment: <!--${this._text}-->]`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as Enums from '../enums/xmlenums.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all XML content nodes (Text, Element, Comment, etc.).
|
|
5
|
+
* Provides core functionality for content representation and type casting.
|
|
6
|
+
*/
|
|
7
|
+
export class FakeContent {
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} type - The content type of this node.
|
|
10
|
+
*/
|
|
11
|
+
constructor(type) {
|
|
12
|
+
this._type = type;
|
|
13
|
+
this._parent = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getType() {
|
|
17
|
+
return this._type;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getParentElement() {
|
|
21
|
+
if (this._parent && this._parent.constructor && this._parent.constructor.name === 'FakeDocument') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return this._parent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setParentElement(parent) {
|
|
28
|
+
this._parent = parent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
detach() {
|
|
32
|
+
if (this._parent && typeof this._parent.removeContent === 'function') {
|
|
33
|
+
this._parent.removeContent(this);
|
|
34
|
+
}
|
|
35
|
+
this._parent = null;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Type Casting Methods ---
|
|
40
|
+
asCdata() { return this._type === Enums.ContentTypes.CDATA ? this : null; }
|
|
41
|
+
asComment() { return this._type === Enums.ContentTypes.COMMENT ? this : null; }
|
|
42
|
+
asDocType() { return this._type === Enums.ContentTypes.DOCTYPE ? this : null; }
|
|
43
|
+
asElement() { return this._type === Enums.ContentTypes.ELEMENT ? this : null; }
|
|
44
|
+
asEntityRef() { return this._type === Enums.ContentTypes.ENTITYREF ? this : null; }
|
|
45
|
+
asProcessingInstruction() { return this._type === Enums.ContentTypes.PROCESSINGINSTRUCTION ? this : null; }
|
|
46
|
+
asText() { return this._type === Enums.ContentTypes.TEXT ? this : null; }
|
|
47
|
+
|
|
48
|
+
getValue() {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|