├── .codeclimate
├── .editorconfig
├── .eslintignore
├── .forceignore
├── .gitignore
├── LICENSE
├── README.md
├── config
└── project-scratch-def.json
├── force-app
└── main
│ └── default
│ └── classes
│ ├── XML.cls
│ ├── XML.cls-meta.xml
│ ├── XMLTest.cls
│ └── XMLTest.cls-meta.xml
├── jest.config.js
├── manifest
└── package.xml
├── package.json
├── sfdc-xml-parser.iml
└── sfdx-project.json
/.codeclimate:
--------------------------------------------------------------------------------
1 | version: 2
2 | checks:
3 | argument-count:
4 | enabled: true
5 | complex-logic:
6 | enabled: true
7 | file-lines:
8 | enabled: true
9 | method-complexity:
10 | enabled: true
11 | method-count:
12 | enabled: true
13 | method-lines:
14 | enabled: true
15 | nested-control-flow:
16 | enabled: true
17 | return-statements:
18 | enabled: true
19 | similar-code:
20 | enabled: true
21 | identical-code:
22 | enabled: true
23 | plugins:
24 | apexmetrics:
25 | enabled: true
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = false
9 | max_line_length = 120
10 | tab_width = 4
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/lwc/**/*.css
2 | **/lwc/**/*.html
3 | **/lwc/**/*.json
4 | **/lwc/**/*.svg
5 | **/lwc/**/*.xml
6 | **/aura/**/*.auradoc
7 | **/aura/**/*.cmp
8 | **/aura/**/*.css
9 | **/aura/**/*.design
10 | **/aura/**/*.evt
11 | **/aura/**/*.json
12 | **/aura/**/*.svg
13 | **/aura/**/*.tokens
14 | **/aura/**/*.xml
15 | **/aura/**/*.app
16 | .sfdx
17 |
--------------------------------------------------------------------------------
/.forceignore:
--------------------------------------------------------------------------------
1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
3 | #
4 |
5 | package.xml
6 |
7 | # LWC configuration files
8 | **/jsconfig.json
9 | **/.eslintrc.json
10 |
11 | # LWC Jest
12 | **/__tests__/**
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
3 | # For useful gitignore templates see: https://github.com/github/gitignore
4 |
5 | # Salesforce cache
6 | .sf/
7 | .sfdx/
8 | .localdevserver/
9 | deploy-options.json
10 |
11 | # LWC VSCode autocomplete
12 | **/lwc/jsconfig.json
13 |
14 | # LWC Jest coverage reports
15 | coverage/
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 | # Dependency directories
25 | node_modules/
26 |
27 | # Eslint cache
28 | .eslintcache
29 |
30 | # MacOS system files
31 | .DS_Store
32 |
33 | # Windows system files
34 | Thumbs.db
35 | ehthumbs.db
36 | [Dd]esktop.ini
37 | $RECYCLE.BIN/
38 |
39 | # Local environment variables
40 | .env
41 |
42 | # Added by Illuminated Cloud
43 | /IlluminatedCloud/
44 | /out/
45 | target/
46 | .idea/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 zabroseric
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SFDC XML Parser
2 |
3 | Built Status:
4 | 
5 | 
6 | [](https://codeclimate.com/github/zabroseric/sfdc-xml-parser/maintainability)
7 |
8 | 
9 | [](https://github.com/zabroseric/sfdc-xml-parser/blob/master/LICENSE)
10 |
11 |
12 | | Deploy to Salesforce Org |
13 | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
14 | | [](https://githubsfdeploy.herokuapp.com/?owner=zabroseric&repo=sfdc-xml-parser&ref=master) |
15 |
16 | ## Table of Contents
17 |
18 | - [SFDC XML Parser](#sfdc-xml-parser)
19 | - [Table of Contents](#table-of-contents)
20 | - [Features](#features)
21 | - [Overview](#overview)
22 | - [Getting Started](#getting-started)
23 | - [Usage - Serialization](#usage---serialization)
24 | - [SObject](#sobject)
25 | - [SObject List](#sobject-list)
26 | - [Objects](#objects)
27 | - [Maps](#maps)
28 | - [Usage - Deserialization](#usage---deserialization)
29 | - [SObject](#sobject-1)
30 | - [SObject List](#sobject-list-1)
31 | - [Objects](#objects-1)
32 | - [Maps](#maps-1)
33 | - [References - Serialization](#references---serialization)
34 | - [Summary](#summary)
35 | - [toString](#tostring)
36 | - [toBase64](#tobase64)
37 | - [debug](#debug)
38 | - [showNulls (default) / suppressNulls](#shownulls-default--suppressnulls)
39 | - [minify (default) / beautify](#minify-default--beautify)
40 | - [hideEncoding (default) / showEncoding](#hideencoding-default--showencoding)
41 | - [addRootAttribute / setRootAttributes](#addrootattribute--setrootattributes)
42 | - [addNamespace / setNamespaces](#addnamespace--setnamespaces)
43 | - [setRootNodeName](#setrootnodename)
44 | - [splitAttributes (default) / embedAttributes](#splitattributes-default--embedattributes)
45 | - [References - Deserialization](#references---deserialization)
46 | - [Summary](#summary-1)
47 | - [toObject](#toobject)
48 | - [setType](#settype)
49 | - [toString](#tostring-1)
50 | - [debug](#debug-1)
51 | - [setReservedWordSuffix](#setreservedwordsuffix)
52 | - [filterNamespace](#filternamespace)
53 | - [showNamespaces (default) / hideNamespaces](#shownamespaces-default--hidenamespaces)
54 | - [addArrayNode / setArrayNodes](#addarraynode--setarraynodes)
55 | - [setRootNode](#setrootnode)
56 | - [sanitize (default) / unsanitize](#sanitize-default--unsanitize)
57 | - [Other Cool Things](#other-cool-things)
58 | - [Deserialization Interfaces](#deserialization-interfaces)
59 | - [Self Keyword](#self-keyword)
60 | - [Node Name Sanatization](#node-name-sanatization)
61 | - [Value Encoding](#value-encoding)
62 | - [Limitations](#limitations)
63 | - [Contributing](#contributing)
64 |
65 | ## Features
66 |
67 | * Serialize / Deserialize SObjects
68 | * Serialize / Deserialize Apex Classes
69 | * Function Chaining
70 | * SObject Node Detection
71 | * Node Name and Value Sanitization
72 | * Clark Notations
73 | * Deserialization Interfaces
74 | * Namespace Filtering
75 | * Reserved Word Management
76 |
77 | ## Overview
78 |
79 | Apex does not currently support XML serialization and deserialization. This functionality is useful when communicating with other systems that support only an XML format, storing files, or even generating HTML. The XML parser bridges this gap by managing the encoding by automatically mapping SObject fields, handling special characters and providing a wide range of flexibility during the encoding processes.
80 |
81 | **Why not create something?**
82 |
83 | Simple - By using a pre-built library like this, no additional development work is needed on your end. Future requirements are met, and the solution has been tested over a wide range of use-cases. Plus we use the solution ourselves in multiple projects. This means that as we or other community members require more functionality, the library is updated. Additionally, as edge cases are found during use, these are fixed.
84 |
85 | ## Getting Started
86 |
87 | The XML Parser uses function chaining to change how the serialization/deserialization is handled. For example, you may want to format the XML in a pretty format with spacing and newlines to help with debugging. To do this, we can simply call the **beautify()** method as per the below:
88 |
89 | ```java
90 | XML.serialize(contact).beautify().toString();
91 | ```
92 |
93 | The result of serialization is as follows:
94 |
95 | ```xml
96 |
97 |
98 | Contact
99 | /services/data/v53.0/sobjects/Contact/0035j00000I09JaAAJ
100 |
101 | First1 Last1
102 | 0035j00000I09JaAAJ
103 |
104 | ```
105 |
106 | The usage section covers common use-cases of these, whereas a list of these can be seen in the [references](#references-serialization) section at the bottom of the readme.
107 |
108 | ## Usage - Serialization
109 |
110 | Examples can be seen below of common serialization use-cases from handling SObjects, to lists and various functions that can be used.
111 |
112 | ### SObject
113 |
114 | The root node is automatically detected, and attributes are added.
115 |
116 | ```java
117 | Contact contact = new Contact(
118 | FirstName = 'First',
119 | LastName = 'Last'
120 | );
121 | insert contact;
122 |
123 | String xmlString = XML.serialize(contact).beautify().toString();
124 | ```
125 |
126 | Result
127 |
128 | ```xml
129 |
130 |
131 | Contact
132 | /services/data/v48.0/sobjects/Contact/0032w000005DrR2AAK
133 |
134 | First
135 | Last
136 | 0032w000005DrR2AAK
137 |
138 | ```
139 |
140 | ### SObject List
141 |
142 | The root node name is converted to a plural that contains child nodes as per the single SObject serialization.
143 |
144 | ```java
145 | List contacts = new List{
146 | new Contact(
147 | FirstName = 'First1',
148 | LastName = 'Last1'
149 | ),
150 | new Contact(
151 | FirstName = 'First2',
152 | LastName = 'Last2'
153 | )
154 | };
155 | insert contacts;
156 |
157 | String xmlString = XML.serialize(contacts).beautify().toString();
158 | ```
159 |
160 | Result
161 |
162 | ```xml
163 |
164 |
165 |
166 | Contact
167 | /services/data/v48.0/sobjects/Contact/0032w000005DrQxAAK
168 |
169 | First1
170 | Last1
171 | 0032w000005DrQxAAK
172 |
173 |
174 |
175 | Contact
176 | /services/data/v48.0/sobjects/Contact/0032w000005DrQyAAK
177 |
178 | First2
179 | Last2
180 | 0032w000005DrQyAAK
181 |
182 |
183 | ```
184 |
185 | ### Objects
186 |
187 | Classes / Objects can be serialized. If the root node name is not set, this will default to either **element** or **elements** depending on if we have a list of objects.
188 |
189 | ```java
190 | Library libraryObject = new Library(
191 | new Catalog(
192 | new Books(
193 | new List{
194 | new Book('title1', new Authors(new List{'Name1', 'Name2'}), '23.00'),
195 | new Book('title1', new Authors(new List{'Name3', 'Name4'}), '23.00')
196 | }
197 | )
198 | )
199 | );
200 |
201 | String xmlString = XML.serialize(libraryObject).setRootNodeName('library').beautify().toString();
202 | ```
203 |
204 | Result
205 |
206 | ```xml
207 |
208 |
209 |
210 |
211 | title1
212 | 23.00
213 |
214 | Name1
215 | Name2
216 |
217 |
218 |
219 | title1
220 | 23.00
221 |
222 | Name3
223 | Name4
224 |
225 |
226 |
227 |
228 |
229 | ```
230 |
231 | ### Maps
232 |
233 | If we are wanting to work with a map/list of primitive types this operates similar to that of objects.
234 |
235 | ```java
236 | String xmlString = XML.serialize(new Map{
237 | 'key1' => 'val1',
238 | 'key2' => 'val2'
239 | }).beautify().debug().toString();
240 | ```
241 |
242 | Result
243 |
244 | ```xml
245 |
246 | val2
247 | val1
248 |
249 | ```
250 |
251 | ## Usage - Deserialization
252 |
253 | ### SObject
254 |
255 | All fields that are common between the XML and SObject are deserialized.
256 |
257 | ```java
258 | Contact contact = (Contact) XML.deserialize('Contact/services/data/v48.0/sobjects/Contact/0032w000005DrR2AAKFirstLast0032w000005DrR2AAK')
259 | .setType(Contact.class).toObject();
260 | ```
261 |
262 | ### SObject List
263 |
264 | A list of SObjects are deserialized if the type is set as a **List<SObject>.class**
265 |
266 | ```java
267 | List contactResult = (List) XML.deserialize('Contact/services/data/v48.0/sobjects/Contact/0032w000005DrQxAAKFirst1Last10032w000005DrQxAAKContact/services/data/v48.0/sobjects/Contact/0032w000005DrQyAAKFirst2Last20032w000005DrQyAAK')
268 | .setType(List.class).toObject();
269 | ```
270 |
271 | ### Objects
272 |
273 | Classes and objects can be deserialized in cases that models are used instead of objects.
274 |
275 | ```java
276 | Library library = XML.deserialize('title123.00Name1Name2title123.00Name3Name4', Library.class)
277 | .toObject();
278 | ```
279 |
280 | ### Maps
281 |
282 | Similarly to serialization, a map/list of primitive types can be deserialized.
283 |
284 | ```java
285 | Map objectMap = (Map) XML.deserialize('val2val1')
286 | .setArrayNode('elements').toObject();
287 | ```
288 |
289 | ## References - Serialization
290 |
291 | ### Summary
292 |
293 | - [toString](#tostring)
294 | - [toBase64](#tobase64)
295 | - [debug](#debug)
296 | - [showNulls (default) / suppressNulls](#shownulls-default--suppressnulls)
297 | - [minify (default) / beautify](#minify-default--beautify)
298 | - [hideEncoding (default) / showEncoding](#hideencoding-default--showencoding)
299 | - [addRootAttribute / setRootAttributes](#addrootattribute--setrootattributes)
300 | - [addNamespace / setNamespaces](#addnamespace--setnamespaces)
301 | - [setRootNodeName](#setrootnodename)
302 | - [splitAttributes (default) / embedAttributes](#splitattributes-default--embedattributes)
303 |
304 | ### toString
305 |
306 | Combines the other functions in the chain sequence to provide the resulting XML in string format.
307 |
308 | ```java
309 | Contact contact = new Contact(
310 | FirstName = 'First',
311 | LastName = 'Last'
312 | );
313 |
314 | String xmlString = XML.serialize(contact)
315 | .setRootNodeName('NewNodeName') // function 1
316 | .showEncoding() // function 2
317 | .beautify() // function 3
318 | .toString(); // Result
319 | ```
320 |
321 | The result in the **xmlString** variable is as follows:
322 |
323 | ```xml
324 |
325 |
326 |
327 | Contact
328 |
329 | First
330 | Last
331 |
332 | ```
333 |
334 | ### toBase64
335 |
336 | Combines the other functions in the chain sequence as like the **toString** method, and encodes the XML result in base64 format.
337 |
338 | ```java
339 | Contact contact = new Contact(
340 | FirstName = 'First',
341 | LastName = 'Last'
342 | );
343 |
344 | String xmlString = XML.serialize(contact)
345 | .setRootNodeName('NewNodeName') // function 1
346 | .showEncoding() // function 2
347 | .beautify() // function 3
348 | .toBase64(); // Result
349 | ```
350 |
351 | The result in the **xmlString** variable is as follows:
352 |
353 | ```plaintext
354 | PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxOZXdUYWc+DQogIDxhdHRyaWJ1dGVzPg0KICAgIDx0eXBlPkNvbnRhY3Q8L3R5cGU+DQogIDwvYXR0cmlidXRlcz4NCiAgPEZpcnN0TmFtZT5GaXJzdDwvRmlyc3ROYW1lPg0KICA8TGFzdE5hbWU+TGFzdDwvTGFzdE5hbWU+DQo8L05ld1RhZz4=
355 | ```
356 |
357 | ### debug
358 |
359 | Prints the XML string to the console using the functions executed previously in the chain. Multiple debugs can be called in the same chain, with each executing independently of the other.
360 |
361 | ```java
362 | Contact contact = new Contact(
363 | FirstName = 'First',
364 | LastName = 'Last'
365 | );
366 |
367 | String xmlString = XML.serialize(contact)
368 | .debug() // Debug 1
369 | .showEncoding().beautify().debug() // Debug 2
370 | .toString();
371 | ```
372 |
373 | Debug 1
374 |
375 | ```xml
376 | ContactFirstLast
377 | ```
378 |
379 | Debug 2
380 |
381 | ```xml
382 |
383 |
384 |
385 | Contact
386 |
387 | First
388 | Last
389 |
390 | ```
391 |
392 | ### showNulls (default) / suppressNulls
393 |
394 | When there are empty or null value node values, by default the value will be rendered within the respective XML node. However, if we want to hide null or empty values, it is possible to use the **suppressNulls** method.
395 |
396 | The result is that any empty nodes are removed until all nodes have values in them.
397 |
398 | ```java
399 | Library library = new Library(
400 | new Catalog(
401 | new Books(
402 | new List{
403 | new Book('title1', new Authors(new List{'Name1', 'Name2'}), '23.00'),
404 | new Book('title5', new Authors(new List{}), null)
405 | }
406 | )
407 | )
408 | );
409 |
410 | XML.serialize(library).suppressNulls().setRootNodeName('library').beautify().debug();
411 | ```
412 |
413 | In the example, the second book does not have any authors. The result is that the author, authors nodes are suppressed alongside the price of the book.
414 |
415 | ```xml
416 |
417 |
418 |
419 |
420 | title1
421 | 23.00
422 |
423 | Name1
424 | Name2
425 |
426 |
427 |
428 | title5
429 |
430 |
431 |
432 |
433 | ```
434 |
435 | ### minify (default) / beautify
436 |
437 | By default the resulting XML has no spaces or new lines between nodes to help with readability. The default behaviour can be overridden by calling the **beautify** method to nicely format the resulting string.
438 |
439 | ```java
440 | Contact contact = new Contact(
441 | FirstName = 'First',
442 | LastName = 'Last'
443 | );
444 |
445 | String xmlStringNormal = XML.serialize(contact).toString();
446 | String xmlStringBeautify = XML.serialize(contact).beautify().toString();
447 | ```
448 |
449 | The result is as follows:
450 |
451 | xmlStringNormal
452 |
453 | ```xml
454 | ContactFirstLast
455 | ```
456 |
457 | xmlStringBeautify
458 |
459 | ```xml
460 |
461 |
462 | Contact
463 |
464 | First
465 | Last
466 |
467 | ```
468 |
469 | ### hideEncoding (default) / showEncoding
470 |
471 | By default the header of the XML is omitted and only the body is present. However when needing to show the header and encoding, this can be done as per the example below:
472 |
473 | ```java
474 | Contact contact = new Contact(
475 | FirstName = 'First',
476 | LastName = 'Last'
477 | );
478 |
479 | String xmlString = XML.serialize(contact).showEncoding().beautify().toString();
480 | ```
481 |
482 | ```xml
483 |
484 |
485 |
486 | Contact
487 |
488 | First
489 | Last
490 |
491 | ```
492 |
493 | ### addRootAttribute / setRootAttributes
494 |
495 | When needing to provide additional attributes this can be set one at a time via the **addRootAttribute** method, or several at a time using the **setRootAttributes** method.
496 |
497 | By default attributes are stored as a child attributes node, however, this can be overridden by the **embedAttributes** method.
498 |
499 | ```java
500 | Contact contact = new Contact(
501 | FirstName = 'First',
502 | LastName = 'Last'
503 | );
504 |
505 | String xmlString = XML.serialize(contact).addRootAttribute('key1', 'value1').addRootAttribute('key2', 'value2').beautify().toString();
506 | ```
507 |
508 | The result is two additional elements within the attributes node are present.
509 |
510 | ```xml
511 |
512 |
513 | Contact
514 | value1
515 | value2
516 |
517 | First
518 | Last
519 |
520 | ```
521 |
522 | ### addNamespace / setNamespaces
523 |
524 | Clark notations support the ability to specify both the XML namespace and 'local name'.
525 | For more information please see the link [here](http://www.jclark.com/xml/xmlns.htm).
526 |
527 | An example of this can be seen below:
528 |
529 | ```java
530 | XML.serialize(new List