ywana-core8 0.1.79 → 0.1.80
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/dist/index.cjs +3244 -2215
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2095 -1125
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +3244 -2215
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +3244 -2215
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/html/ExampleLayout.css +401 -0
- package/src/html/ExampleLayout.js +192 -0
- package/src/html/README-sidebar-navigation.md +195 -0
- package/src/html/accordion.example.js +123 -4
- package/src/html/accordion.example.js.backup +390 -0
- package/src/html/button.example.js +50 -3
- package/src/html/button.example.js.backup +374 -0
- package/src/html/button.example.new.js +416 -0
- package/src/html/checkbox.example.js +93 -4
- package/src/html/checkbox.example.js.backup +316 -0
- package/src/html/chip.example.js +108 -4
- package/src/html/chip.example.js.backup +355 -0
- package/src/html/color.example.js +108 -4
- package/src/html/color.example.js.backup +527 -0
- package/src/html/components.example.js +123 -4
- package/src/html/components.example.js.backup +492 -0
- package/src/html/convert-examples.js +183 -0
- package/src/html/demo-sidebar.html +410 -0
- package/src/html/form.example.js +93 -4
- package/src/html/form.example.js.backup +385 -0
- package/src/html/header2.example.js +108 -4
- package/src/html/header2.example.js.backup +411 -0
- package/src/html/icon.example.js +77 -3
- package/src/html/icon.example.js.backup +268 -0
- package/src/html/list.example.js +93 -4
- package/src/html/list.example.js.backup +404 -0
- package/src/html/progress.example.js +74 -4
- package/src/html/progress.example.js.backup +424 -0
- package/src/html/property.example.js +123 -4
- package/src/html/property.example.js.backup +553 -0
- package/src/html/radio.example.js +108 -4
- package/src/html/radio.example.js.backup +389 -0
- package/src/html/section.example.js +42 -3
- package/src/html/section.example.js.backup +99 -0
- package/src/html/switch.example.js +108 -4
- package/src/html/switch.example.js.backup +461 -0
- package/src/html/tab.example.js +93 -4
- package/src/html/tab.example.js.backup +446 -0
- package/src/html/table-export-utils.js +483 -0
- package/src/html/table-summary-functions.js +363 -0
- package/src/html/table2.css +1449 -479
- package/src/html/table2.example.js +2937 -512
- package/src/html/table2.example.js.broken +1226 -0
- package/src/html/table2.js +1426 -1000
- package/src/html/test-resize.html +279 -0
- package/src/html/test-selection.html +387 -0
- package/src/html/textfield2.example.js +108 -4
- package/src/html/textfield2.example.js.backup +1370 -0
- package/src/html/tokenfield.example.js +108 -4
- package/src/html/tokenfield.example.js.backup +503 -0
- package/src/html/tree.css +2 -4
- package/src/html/tree.example.js +93 -4
- package/src/html/tree.example.js.backup +475 -0
- package/src/html/tree.js +19 -3
@@ -0,0 +1,483 @@
|
|
1
|
+
/**
|
2
|
+
* Utilidades de exportación para DataTable2
|
3
|
+
* Proporciona funciones para exportar datos a diferentes formatos
|
4
|
+
*/
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Detectar entorno de ejecución
|
8
|
+
*/
|
9
|
+
const detectEnvironment = () => {
|
10
|
+
const isInIframe = window !== window.top
|
11
|
+
const isPreviewJs = window.location.href.includes('previewjs') ||
|
12
|
+
window.location.href.includes('localhost:3140')
|
13
|
+
const isLocalhost = window.location.hostname === 'localhost' ||
|
14
|
+
window.location.hostname === '127.0.0.1'
|
15
|
+
|
16
|
+
return {
|
17
|
+
isInIframe,
|
18
|
+
isPreviewJs,
|
19
|
+
isLocalhost,
|
20
|
+
isRestricted: isInIframe || isPreviewJs
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Exportar datos a CSV
|
26
|
+
*/
|
27
|
+
export const exportToCSV = (data, columns, filename = 'table-export.csv') => {
|
28
|
+
try {
|
29
|
+
const env = detectEnvironment()
|
30
|
+
|
31
|
+
// Crear headers
|
32
|
+
const headers = columns.map(col => col.label || col.id)
|
33
|
+
|
34
|
+
// Crear filas de datos
|
35
|
+
const rows = data.map(row =>
|
36
|
+
columns.map(col => {
|
37
|
+
let value = row[col.id]
|
38
|
+
|
39
|
+
// Manejar valores especiales
|
40
|
+
if (value === null || value === undefined) {
|
41
|
+
value = ''
|
42
|
+
} else if (typeof value === 'object') {
|
43
|
+
value = JSON.stringify(value)
|
44
|
+
} else if (typeof value === 'string') {
|
45
|
+
// Escapar comillas dobles
|
46
|
+
value = `"${value.replace(/"/g, '""')}"`
|
47
|
+
} else {
|
48
|
+
value = String(value)
|
49
|
+
}
|
50
|
+
|
51
|
+
return value
|
52
|
+
})
|
53
|
+
)
|
54
|
+
|
55
|
+
// Combinar headers y datos
|
56
|
+
const csvContent = [
|
57
|
+
headers.join(','),
|
58
|
+
...rows.map(row => row.join(','))
|
59
|
+
].join('\n')
|
60
|
+
|
61
|
+
// Crear y descargar archivo
|
62
|
+
downloadFile(csvContent, filename, 'text/csv;charset=utf-8;')
|
63
|
+
|
64
|
+
const message = env.isRestricted
|
65
|
+
? `Exportado ${data.length} filas a CSV (se abrirá en nueva ventana)`
|
66
|
+
: `Exportado ${data.length} filas a CSV`
|
67
|
+
|
68
|
+
return { success: true, message }
|
69
|
+
} catch (error) {
|
70
|
+
console.error('Error exporting to CSV:', error)
|
71
|
+
return { success: false, message: 'Error al exportar a CSV' }
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Exportar datos a JSON
|
77
|
+
*/
|
78
|
+
export const exportToJSON = (data, columns, filename = 'table-export.json') => {
|
79
|
+
try {
|
80
|
+
const env = detectEnvironment()
|
81
|
+
|
82
|
+
// Filtrar solo las columnas visibles
|
83
|
+
const filteredData = data.map(row => {
|
84
|
+
const filteredRow = {}
|
85
|
+
columns.forEach(col => {
|
86
|
+
filteredRow[col.id] = row[col.id]
|
87
|
+
})
|
88
|
+
return filteredRow
|
89
|
+
})
|
90
|
+
|
91
|
+
const jsonContent = JSON.stringify(filteredData, null, 2)
|
92
|
+
|
93
|
+
downloadFile(jsonContent, filename, 'application/json;charset=utf-8;')
|
94
|
+
|
95
|
+
const message = env.isRestricted
|
96
|
+
? `Exportado ${data.length} filas a JSON (se abrirá en nueva ventana)`
|
97
|
+
: `Exportado ${data.length} filas a JSON`
|
98
|
+
|
99
|
+
return { success: true, message }
|
100
|
+
} catch (error) {
|
101
|
+
console.error('Error exporting to JSON:', error)
|
102
|
+
return { success: false, message: 'Error al exportar a JSON' }
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Exportar datos a Excel (formato TSV compatible)
|
108
|
+
*/
|
109
|
+
export const exportToExcel = (data, columns, filename = 'table-export.xlsx') => {
|
110
|
+
try {
|
111
|
+
// Crear headers
|
112
|
+
const headers = columns.map(col => col.label || col.id)
|
113
|
+
|
114
|
+
// Crear filas de datos (usando tabulaciones para Excel)
|
115
|
+
const rows = data.map(row =>
|
116
|
+
columns.map(col => {
|
117
|
+
let value = row[col.id]
|
118
|
+
|
119
|
+
if (value === null || value === undefined) {
|
120
|
+
value = ''
|
121
|
+
} else if (typeof value === 'object') {
|
122
|
+
value = JSON.stringify(value)
|
123
|
+
} else {
|
124
|
+
value = String(value)
|
125
|
+
}
|
126
|
+
|
127
|
+
return value
|
128
|
+
})
|
129
|
+
)
|
130
|
+
|
131
|
+
// Combinar con tabulaciones (TSV)
|
132
|
+
const tsvContent = [
|
133
|
+
headers.join('\t'),
|
134
|
+
...rows.map(row => row.join('\t'))
|
135
|
+
].join('\n')
|
136
|
+
|
137
|
+
// Usar extensión .xls para compatibilidad
|
138
|
+
const excelFilename = filename.replace('.xlsx', '.xls')
|
139
|
+
downloadFile(tsvContent, excelFilename, 'application/vnd.ms-excel;charset=utf-8;')
|
140
|
+
|
141
|
+
return { success: true, message: `Exportado ${data.length} filas a Excel` }
|
142
|
+
} catch (error) {
|
143
|
+
console.error('Error exporting to Excel:', error)
|
144
|
+
return { success: false, message: 'Error al exportar a Excel' }
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Exportar datos a XML
|
150
|
+
*/
|
151
|
+
export const exportToXML = (data, columns, filename = 'table-export.xml') => {
|
152
|
+
try {
|
153
|
+
let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<data>\n'
|
154
|
+
|
155
|
+
data.forEach(row => {
|
156
|
+
xmlContent += ' <row>\n'
|
157
|
+
columns.forEach(col => {
|
158
|
+
const value = row[col.id]
|
159
|
+
const escapedValue = escapeXML(value)
|
160
|
+
xmlContent += ` <${col.id}>${escapedValue}</${col.id}>\n`
|
161
|
+
})
|
162
|
+
xmlContent += ' </row>\n'
|
163
|
+
})
|
164
|
+
|
165
|
+
xmlContent += '</data>'
|
166
|
+
|
167
|
+
downloadFile(xmlContent, filename, 'application/xml;charset=utf-8;')
|
168
|
+
|
169
|
+
return { success: true, message: `Exportado ${data.length} filas a XML` }
|
170
|
+
} catch (error) {
|
171
|
+
console.error('Error exporting to XML:', error)
|
172
|
+
return { success: false, message: 'Error al exportar a XML' }
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
/**
|
177
|
+
* Exportar solo filas seleccionadas
|
178
|
+
*/
|
179
|
+
export const exportSelected = (data, columns, selectedRows, format = 'csv') => {
|
180
|
+
const selectedData = data.filter(row => selectedRows.includes(row.id))
|
181
|
+
|
182
|
+
if (selectedData.length === 0) {
|
183
|
+
return { success: false, message: 'No hay filas seleccionadas para exportar' }
|
184
|
+
}
|
185
|
+
|
186
|
+
const filename = `selected-rows.${format}`
|
187
|
+
|
188
|
+
switch (format) {
|
189
|
+
case 'csv':
|
190
|
+
return exportToCSV(selectedData, columns, filename)
|
191
|
+
case 'json':
|
192
|
+
return exportToJSON(selectedData, columns, filename)
|
193
|
+
case 'excel':
|
194
|
+
return exportToExcel(selectedData, columns, filename)
|
195
|
+
case 'xml':
|
196
|
+
return exportToXML(selectedData, columns, filename)
|
197
|
+
default:
|
198
|
+
return { success: false, message: 'Formato no soportado' }
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
/**
|
203
|
+
* Función auxiliar para descargar archivos
|
204
|
+
* Maneja diferentes entornos incluyendo preview.js y iframes
|
205
|
+
*/
|
206
|
+
const downloadFile = (content, filename, mimeType) => {
|
207
|
+
try {
|
208
|
+
const blob = new Blob([content], { type: mimeType })
|
209
|
+
|
210
|
+
// Método 1: Intentar descarga directa (funciona en la mayoría de navegadores)
|
211
|
+
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
212
|
+
// IE/Edge
|
213
|
+
window.navigator.msSaveOrOpenBlob(blob, filename)
|
214
|
+
return
|
215
|
+
}
|
216
|
+
|
217
|
+
// Método 2: Crear URL y link (estándar)
|
218
|
+
const url = URL.createObjectURL(blob)
|
219
|
+
|
220
|
+
// Verificar si estamos en un iframe o entorno restringido
|
221
|
+
const isInIframe = window !== window.top
|
222
|
+
const isPreviewJs = window.location.href.includes('previewjs') ||
|
223
|
+
window.location.href.includes('localhost:3140')
|
224
|
+
|
225
|
+
if (isInIframe || isPreviewJs) {
|
226
|
+
// Método alternativo para entornos restringidos
|
227
|
+
downloadFileAlternative(content, filename, mimeType, blob, url)
|
228
|
+
} else {
|
229
|
+
// Método estándar
|
230
|
+
downloadFileStandard(url, filename)
|
231
|
+
}
|
232
|
+
|
233
|
+
// Limpiar URL después de un tiempo
|
234
|
+
setTimeout(() => {
|
235
|
+
try {
|
236
|
+
URL.revokeObjectURL(url)
|
237
|
+
} catch (e) {
|
238
|
+
// Ignorar errores de limpieza
|
239
|
+
}
|
240
|
+
}, 2000)
|
241
|
+
|
242
|
+
} catch (error) {
|
243
|
+
console.error('Error downloading file:', error)
|
244
|
+
// Fallback: mostrar contenido en nueva ventana
|
245
|
+
showContentInNewWindow(content, filename, mimeType)
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Descarga estándar con link
|
251
|
+
*/
|
252
|
+
const downloadFileStandard = (url, filename) => {
|
253
|
+
const link = document.createElement('a')
|
254
|
+
link.href = url
|
255
|
+
link.download = filename
|
256
|
+
link.style.display = 'none'
|
257
|
+
link.target = '_blank'
|
258
|
+
link.rel = 'noopener noreferrer'
|
259
|
+
|
260
|
+
document.body.appendChild(link)
|
261
|
+
|
262
|
+
// Usar setTimeout para asegurar que el elemento esté en el DOM
|
263
|
+
setTimeout(() => {
|
264
|
+
try {
|
265
|
+
link.click()
|
266
|
+
} catch (e) {
|
267
|
+
// Si click() falla, intentar dispatchEvent
|
268
|
+
const event = new MouseEvent('click', {
|
269
|
+
view: window,
|
270
|
+
bubbles: true,
|
271
|
+
cancelable: true
|
272
|
+
})
|
273
|
+
link.dispatchEvent(event)
|
274
|
+
}
|
275
|
+
|
276
|
+
// Remover el link después de un breve delay
|
277
|
+
setTimeout(() => {
|
278
|
+
if (link.parentNode) {
|
279
|
+
document.body.removeChild(link)
|
280
|
+
}
|
281
|
+
}, 100)
|
282
|
+
}, 10)
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Método alternativo para entornos restringidos
|
287
|
+
*/
|
288
|
+
const downloadFileAlternative = (content, filename, mimeType, blob, url) => {
|
289
|
+
try {
|
290
|
+
// Método 1: Intentar abrir en nueva ventana con data URL
|
291
|
+
const dataUrl = `data:${mimeType};charset=utf-8,${encodeURIComponent(content)}`
|
292
|
+
|
293
|
+
// Verificar tamaño del contenido (data URLs tienen límites)
|
294
|
+
if (content.length < 1000000) { // 1MB límite aproximado
|
295
|
+
const newWindow = window.open(dataUrl, '_blank')
|
296
|
+
if (newWindow) {
|
297
|
+
// Sugerir al usuario que guarde el archivo
|
298
|
+
setTimeout(() => {
|
299
|
+
try {
|
300
|
+
newWindow.document.title = `Descargar ${filename}`
|
301
|
+
const instruction = newWindow.document.createElement('div')
|
302
|
+
instruction.innerHTML = `
|
303
|
+
<div style="padding: 20px; font-family: Arial, sans-serif;">
|
304
|
+
<h3>Archivo listo para descargar</h3>
|
305
|
+
<p>Nombre: <strong>${filename}</strong></p>
|
306
|
+
<p>Para guardar: <kbd>Ctrl+S</kbd> (Windows) o <kbd>Cmd+S</kbd> (Mac)</p>
|
307
|
+
<button onclick="window.close()" style="padding: 10px 20px; margin-top: 10px;">Cerrar</button>
|
308
|
+
</div>
|
309
|
+
`
|
310
|
+
newWindow.document.body.insertBefore(instruction, newWindow.document.body.firstChild)
|
311
|
+
} catch (e) {
|
312
|
+
// Ignorar errores de manipulación del DOM en la nueva ventana
|
313
|
+
}
|
314
|
+
}, 100)
|
315
|
+
return
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
// Método 2: Copiar al portapapeles como fallback
|
320
|
+
copyToClipboard(content, filename)
|
321
|
+
|
322
|
+
} catch (error) {
|
323
|
+
console.error('Error in alternative download:', error)
|
324
|
+
// Último recurso: mostrar en consola
|
325
|
+
console.log(`Contenido del archivo ${filename}:`, content)
|
326
|
+
alert(`No se pudo descargar el archivo. El contenido se ha mostrado en la consola del navegador.`)
|
327
|
+
}
|
328
|
+
}
|
329
|
+
|
330
|
+
/**
|
331
|
+
* Mostrar contenido en nueva ventana como último recurso
|
332
|
+
*/
|
333
|
+
const showContentInNewWindow = (content, filename, mimeType) => {
|
334
|
+
try {
|
335
|
+
const newWindow = window.open('', '_blank')
|
336
|
+
if (newWindow) {
|
337
|
+
newWindow.document.write(`
|
338
|
+
<html>
|
339
|
+
<head>
|
340
|
+
<title>${filename}</title>
|
341
|
+
<style>
|
342
|
+
body { font-family: monospace; padding: 20px; }
|
343
|
+
.header { background: #f0f0f0; padding: 10px; margin-bottom: 20px; }
|
344
|
+
.content { white-space: pre-wrap; }
|
345
|
+
</style>
|
346
|
+
</head>
|
347
|
+
<body>
|
348
|
+
<div class="header">
|
349
|
+
<h3>Archivo: ${filename}</h3>
|
350
|
+
<p>Tipo: ${mimeType}</p>
|
351
|
+
<p>Para guardar: Ctrl+S (Windows) o Cmd+S (Mac)</p>
|
352
|
+
</div>
|
353
|
+
<div class="content">${content.replace(/</g, '<').replace(/>/g, '>')}</div>
|
354
|
+
</body>
|
355
|
+
</html>
|
356
|
+
`)
|
357
|
+
newWindow.document.close()
|
358
|
+
}
|
359
|
+
} catch (error) {
|
360
|
+
console.error('Error showing content in new window:', error)
|
361
|
+
alert('No se pudo descargar el archivo. Verifique la consola del navegador.')
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Copiar contenido al portapapeles
|
367
|
+
*/
|
368
|
+
const copyToClipboard = async (content, filename) => {
|
369
|
+
try {
|
370
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
371
|
+
await navigator.clipboard.writeText(content)
|
372
|
+
alert(`Contenido del archivo "${filename}" copiado al portapapeles. Puedes pegarlo en un editor de texto y guardarlo.`)
|
373
|
+
} else {
|
374
|
+
// Fallback para navegadores sin Clipboard API
|
375
|
+
const textArea = document.createElement('textarea')
|
376
|
+
textArea.value = content
|
377
|
+
textArea.style.position = 'fixed'
|
378
|
+
textArea.style.opacity = '0'
|
379
|
+
document.body.appendChild(textArea)
|
380
|
+
textArea.select()
|
381
|
+
|
382
|
+
try {
|
383
|
+
document.execCommand('copy')
|
384
|
+
alert(`Contenido del archivo "${filename}" copiado al portapapeles.`)
|
385
|
+
} catch (e) {
|
386
|
+
console.log('Fallback copy failed:', e)
|
387
|
+
}
|
388
|
+
|
389
|
+
document.body.removeChild(textArea)
|
390
|
+
}
|
391
|
+
} catch (error) {
|
392
|
+
console.error('Error copying to clipboard:', error)
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
/**
|
397
|
+
* Función auxiliar para escapar XML
|
398
|
+
*/
|
399
|
+
const escapeXML = (value) => {
|
400
|
+
if (value === null || value === undefined) {
|
401
|
+
return ''
|
402
|
+
}
|
403
|
+
|
404
|
+
return String(value)
|
405
|
+
.replace(/&/g, '&')
|
406
|
+
.replace(/</g, '<')
|
407
|
+
.replace(/>/g, '>')
|
408
|
+
.replace(/"/g, '"')
|
409
|
+
.replace(/'/g, ''')
|
410
|
+
}
|
411
|
+
|
412
|
+
/**
|
413
|
+
* Obtener estadísticas de exportación
|
414
|
+
*/
|
415
|
+
export const getExportStats = (data, columns) => {
|
416
|
+
return {
|
417
|
+
totalRows: data.length,
|
418
|
+
totalColumns: columns.length,
|
419
|
+
visibleColumns: columns.filter(col => !col.hidden).length,
|
420
|
+
estimatedSize: {
|
421
|
+
csv: Math.round((data.length * columns.length * 10) / 1024) + ' KB',
|
422
|
+
json: Math.round((JSON.stringify(data).length) / 1024) + ' KB'
|
423
|
+
}
|
424
|
+
}
|
425
|
+
}
|
426
|
+
|
427
|
+
/**
|
428
|
+
* Validar datos antes de exportar
|
429
|
+
*/
|
430
|
+
export const validateExportData = (data, columns) => {
|
431
|
+
const errors = []
|
432
|
+
|
433
|
+
if (!data || data.length === 0) {
|
434
|
+
errors.push('No hay datos para exportar')
|
435
|
+
}
|
436
|
+
|
437
|
+
if (!columns || columns.length === 0) {
|
438
|
+
errors.push('No hay columnas definidas')
|
439
|
+
}
|
440
|
+
|
441
|
+
return {
|
442
|
+
isValid: errors.length === 0,
|
443
|
+
errors
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
/**
|
448
|
+
* Formatos de exportación disponibles
|
449
|
+
*/
|
450
|
+
export const exportFormats = {
|
451
|
+
csv: {
|
452
|
+
id: 'csv',
|
453
|
+
label: 'CSV',
|
454
|
+
description: 'Valores separados por comas',
|
455
|
+
icon: 'description',
|
456
|
+
mimeType: 'text/csv',
|
457
|
+
extension: '.csv'
|
458
|
+
},
|
459
|
+
excel: {
|
460
|
+
id: 'excel',
|
461
|
+
label: 'Excel',
|
462
|
+
description: 'Hoja de cálculo de Microsoft Excel',
|
463
|
+
icon: 'table_chart',
|
464
|
+
mimeType: 'application/vnd.ms-excel',
|
465
|
+
extension: '.xls'
|
466
|
+
},
|
467
|
+
json: {
|
468
|
+
id: 'json',
|
469
|
+
label: 'JSON',
|
470
|
+
description: 'JavaScript Object Notation',
|
471
|
+
icon: 'code',
|
472
|
+
mimeType: 'application/json',
|
473
|
+
extension: '.json'
|
474
|
+
},
|
475
|
+
xml: {
|
476
|
+
id: 'xml',
|
477
|
+
label: 'XML',
|
478
|
+
description: 'Extensible Markup Language',
|
479
|
+
icon: 'code',
|
480
|
+
mimeType: 'application/xml',
|
481
|
+
extension: '.xml'
|
482
|
+
}
|
483
|
+
}
|