├── .github
└── workflows
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src
├── collection.ts
├── const.ts
├── convert.ts
├── converters.ts
├── decorators.ts
├── error.ts
├── index.ts
├── namespace_manager.ts
├── types.ts
├── utils.ts
├── xml.ts
├── xml_collection.ts
└── xml_object.ts
├── test
├── collection.ts
├── convert.ts
├── converters.ts
├── decorators.ts
├── error.ts
├── get_load.ts
├── namespace_manager.ts
├── object.ts
├── utils.ts
└── xml_collection.ts
├── tsconfig.json
├── tsconfig.types.json
├── tslint.json
└── yarn.lock
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | tags:
5 | - v[0-9]+.[0-9]+.[0-9]+
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: "16.x"
14 | - name: Install dependencies
15 | run: yarn
16 | - name: Build
17 | run: npm run build
18 | - name: Publish to NPM
19 | run: |
20 | npm set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN
21 | npm publish --access public
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
24 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [14.x, 16.x]
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 |
21 | - name: Get yarn cache directory path
22 | id: yarn-cache-dir-path
23 | run: echo "::set-output name=dir::$(yarn cache dir)"
24 |
25 | - uses: actions/cache@v3
26 | id: yarn-cache
27 | with:
28 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
29 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
30 | restore-keys: |
31 | ${{ runner.os }}-yarn-
32 |
33 | - name: Install dependencies
34 | run: yarn
35 |
36 | - name: Run linter
37 | run: npm run lint
38 |
39 | - name: Run test with coverage
40 | run: npm run coverage
41 |
42 | # Fixes problem with incorrect SF paths. See https://github.com/coverallsapp/github-action/issues/125
43 | - name: Update lcov.info
44 | run: |
45 | sed -E "s/SF:(.+file:(.+))/SF:\2/g" ./coverage/lcov.info > coverage/lcov.new.info
46 | mv ./coverage/lcov.new.info ./coverage/lcov.info
47 |
48 | - name: Coveralls
49 | uses: coverallsapp/github-action@master
50 | with:
51 | github-token: ${{ secrets.GITHUB_TOKEN }}
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /build
3 | /node_modules
4 | /.nyc_output
5 | /coverage
6 | /types
7 | /lib
8 | /dist
9 | /test/*.js
10 | /test/!tsconfig.js
11 | debug.log
12 |
13 | # Mac
14 | .DS_Store
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /.circleci
3 | /node_modules
4 | /.nyc_output
5 | /coverage
6 | /src
7 | /test/*.ts
8 | /test/tsconfig.json
9 | /circle.yml
10 | /rollup*.js
11 | /ts*.json
12 | /**/*.js.map
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016
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 |
2 | # xml-core
3 |
4 | `xml-core` is a set of classes that make it easier to work with XML within the browser and node.
5 |
6 | [](https://raw.githubusercontent.com/PeculiarVentures/xml-core/master/LICENSE)
7 | [](https://github.com/PeculiarVentures/xml-core/actions/workflows/test.yml)
8 | [](https://coveralls.io/github/PeculiarVentures/xml-core?branch=master)
9 | [](http://badge.fury.io/js/xml-core)
10 |
11 | [](https://nodei.co/npm/xml-core/)
12 |
13 |
14 | ## Introduction
15 |
16 | We wanted to be able to validate [XAdES](https://en.wikipedia.org/wiki/XAdES) in the browser, specifically so we could validate the signature on the [EU Trust List](https://github.com/PeculiarVentures/tl-create).
17 |
18 |
19 | This lead us to the creation od [XMLDSIGjs](https://github.com/PeculiarVentures/xmldsigjs) which allows us to validate XML and [XAdESjs](https://github.com/PeculiarVentures/xadesjs) which extends it and enables us to validate XAdES signatures.
20 |
21 | We use `xml-core` to make the creation of these libraries easier, we hope you may find it valuable in your own projects also.
22 |
23 | Fundementally `xml-core` provides a way to transform XML to JSON and JSON to XML, which enables you to enforce a schema on the associated XML. The goal of this is to let you work naturally with XML in Javascript.
24 |
25 | It is similar to [xmljs](https://www.npmjs.com/package/xmljs) but has a few differences -
26 | - Can convert the JSON back to XML,
27 | - Uses [decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.61eut6wa9) to make enforcing schema in Javascript more natural.
28 |
29 | ## Install
30 |
31 | ```
32 | npm install xml-core
33 | ```
34 |
35 | ## Using
36 |
37 | ### ES5
38 |
39 | ```javascript
40 | var XmlCore = require("xml-core");
41 | ```
42 |
43 | ### ES2015
44 |
45 | ```javascript
46 | import XmlCore from "xml-core";
47 | ```
48 |
49 | ## Decrators
50 |
51 | Information about decorators [ES2015](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.qnl62mocp), [TypeScript](https://www.typescriptlang.org/docs/handbook/decorators.html)
52 |
53 | ### XmlElement
54 |
55 | Class decorator which allows to describe schema for xml element
56 |
57 | __Paramteres__
58 |
59 | | Name | Description |
60 | |:----------------|:--------------------------------------------------------------------------------|
61 | | localName | Sets a local name for xml element. Default value is name of Class |
62 | | namespaceURI | Sets a namespace URI for xml element. Default value is `null` |
63 | | prefix | Sets a prefix for xml element. Default value is `null` |
64 | | parser | Sets a parser as `XmlObject` for each child element of `XmlCollection`. Optional|
65 |
66 | ### XmlAttribute
67 |
68 | Property decorator which allows to describe schema for attribute of xml element
69 |
70 | __Paramteres__
71 |
72 | | Name | Description |
73 | |:----------------|:--------------------------------------------------------------------------------|
74 | | localName | Sets a local name for xml element. Default value is name of Property |
75 | | namespaceURI | Sets a namespace URI for xml element. Default value is `null` |
76 | | prefix | Sets a prefix for attribute of xml element. Default value is `null` |
77 | | defaultValue | Sets a default value for attribute of xml element. Optional |
78 | | required | Determines if attribute of xml element is required. Default value is `false` |
79 | | converter | Sets a specific converter for attribute of xml element. Default is simple text |
80 |
81 |
82 | ### XmlChildElement
83 |
84 | Property decorator which allows to describe schema for child element of xml element
85 |
86 | __Paramteres__
87 |
88 | | Name | Description |
89 | |:----------------|:--------------------------------------------------------------------------------|
90 | | localName | Sets local name for xml element. Default value is name of Class |
91 | | namespaceURI | Sets namespace URI for xml element. Default value is `null` |
92 | | prefix | Sets prefix for xml element. Default value is `null` |
93 | | defaultValue | Sets a default value for attribute of xml element. Optional |
94 | | required | Determines if child element is required. Default value is `false` |
95 | | converter | Sets a specific converter for child element. Default is simple text |
96 | | parser | Sets parser as `XmlObject` for child element. Optional |
97 | | minOccurs | Sets a min value for child element occurs. Default value is `0` |
98 | | maxOccurs | Sets a max value for child element occurs. Default value is `MAX` |
99 | | noRoot | Determines if parser as `XmlCollection` must return it's children to parent element |
100 |
101 | ### XmlContent
102 |
103 | Property decorator which allows to describe schema for content of xml element
104 |
105 | __Paramteres__
106 |
107 | | Name | Description |
108 | |-----------------|---------------------------------------------------------------------------------|
109 | | defaultValue | Sets a default value for content of xml element. Optional |
110 | | required | Determines if content of xml element is required. Default value is `false` |
111 | | converter | Sets a specific converter for content of xml element. Default is simple text |
112 |
113 | ## XmlObject
114 |
115 | Base class for XML elements.
116 |
117 | ### LoadXml
118 |
119 | Reads XML from string
120 |
121 | ```typescript
122 | LoadXml(node: Node | string): void;
123 | static LoadXml(node: Node | string): this;
124 | ```
125 |
126 | ### GetXml
127 |
128 | Writes object to XML node
129 |
130 | ```typescript
131 | GetXml(): Node | null;
132 | ```
133 |
134 | ### toString
135 |
136 | Writes object to string
137 |
138 | ```
139 | toString(): string;
140 | ```
141 |
142 | __Example__
143 |
144 | Target XML [schema]()
145 |
146 | ```xml
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | ```
158 |
159 | TypeScript implementation of XML schema
160 |
161 | ```typescript
162 | import { XmlObject, XmlBase64Converter } from "xml-core";
163 |
164 | @XmlElement({
165 | localName: "Signature",
166 | namespaceURI: "http://www.w3.org/2000/09/xmldsig#",
167 | prefix: "ds"
168 | })
169 | class Signature extends XmlObject {
170 |
171 | @XmlAttribute({
172 | localName: XmlSignature.AttributeNames.Id,
173 | defaultValue: "",
174 | })
175 | public Id: string;
176 |
177 | @XmlChildElement({
178 | parser: SignedInfo,
179 | required: true,
180 | })
181 | public SignedInfo: SignedInfo;
182 |
183 | @XmlChildElement({
184 | localName: "SignatureValue",
185 | namespaceURI: "http://www.w3.org/2000/09/xmldsig#",
186 | prefix: "ds",
187 | required: true,
188 | converter: XmlBase64Converter,
189 | defaultValue: null,
190 | })
191 | public SignatureValue: Uint8Array | null;
192 |
193 | @XmlChildElement({
194 | parser: KeyInfo
195 | })
196 | public KeyInfo: KeyInfo;
197 |
198 | @XmlChildElement({
199 | parser: DataObjects,
200 | noRoot: true
201 | })
202 | public ObjectList: DataObjects;
203 |
204 | }
205 | ```
206 |
207 | __Using__
208 |
209 | ```typescript
210 | const signature = new Signature();
211 |
212 | // Read XML
213 | signature.LoadXml(Signature.Parse('...'));
214 | console.log("Id:", signature.Id); // Id: sigId
215 |
216 | // Write XML
217 | signature.Id = "newId";
218 | console.log(signature.toString()); // ...
219 | ```
220 |
221 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xml-core",
3 | "version": "1.1.5",
4 | "description": "`xml-core` is a set of classes that make it easier to work with XML within the browser and node.",
5 | "main": "dist/index.js",
6 | "module": "dist/index.es.js",
7 | "types": "dist/types/index.d.ts",
8 | "directories": {
9 | "test": "test"
10 | },
11 | "dependencies": {
12 | "@xmldom/xmldom": "^0.8.2",
13 | "tslib": "^2.4.0",
14 | "xpath.js": "^1.1.0"
15 | },
16 | "devDependencies": {
17 | "@types/mocha": "^9.1.1",
18 | "@types/node": "^18.6.3",
19 | "coveralls": "^3.1.1",
20 | "mocha": "^10.0.0",
21 | "nyc": "^15.1.0",
22 | "rimraf": "^3.0.2",
23 | "rollup": "^2.77.2",
24 | "rollup-plugin-typescript2": "^0.32.1",
25 | "ts-node": "^10.9.1",
26 | "tslint": "^6.1.3",
27 | "typescript": "^4.7.4"
28 | },
29 | "scripts": {
30 | "test": "mocha",
31 | "upgrade": "yarn upgrade-interactive --latest",
32 | "prepare": "npm run build",
33 | "clear": "rimraf dist/",
34 | "build": "npm run build:module && npm run build:types",
35 | "build:module": "rollup -c",
36 | "build:types": "tsc -p tsconfig.types.json",
37 | "rebuild": "npm run clear && npm run build",
38 | "lint": "tslint 'src/**/*.ts'",
39 | "pub": "npm run pub:patch",
40 | "pub:major": "npm version major",
41 | "pub:minor": "npm version minor",
42 | "pub:patch": "npm version patch",
43 | "postpub": "git push --follow-tags",
44 | "coverage": "nyc npm test",
45 | "coveralls": "nyc report --reporter=text-lcov | coveralls"
46 | },
47 | "repository": {
48 | "type": "git",
49 | "url": "git+https://github.com/PeculiarVentures/xml-core.git"
50 | },
51 | "keywords": [
52 | "xml"
53 | ],
54 | "author": "microshine",
55 | "license": "MIT",
56 | "bugs": {
57 | "url": "https://github.com/PeculiarVentures/xml-core.git/issues"
58 | },
59 | "homepage": "https://github.com/PeculiarVentures/xml-core.git#readme",
60 | "nyc": {
61 | "extension": [
62 | ".ts",
63 | ".tsx"
64 | ],
65 | "include": [
66 | "src/**/*.ts"
67 | ],
68 | "exclude": [
69 | "**/*.d.ts"
70 | ],
71 | "reporter": [
72 | "text-summary",
73 | "lcov"
74 | ]
75 | },
76 | "mocha": {
77 | "require": "ts-node/register",
78 | "extension": [
79 | "ts"
80 | ],
81 | "watch-files": [
82 | "test/**/*.ts"
83 | ]
84 | },
85 | "resolutions": {
86 | "ansi-regex": "^5.0.1",
87 | "path-parse": "^1.0.7",
88 | "browserslist": "^4.16.5",
89 | "json-schema": "^0.4.0",
90 | "minimist": "^1.2.6"
91 | }
92 | }
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "rollup-plugin-typescript2";
2 | import pkg from "./package.json";
3 |
4 | const banner = [].join("\n");
5 | const input = "src/index.ts";
6 | const external = Object.keys(pkg.dependencies);
7 |
8 | export default {
9 | input,
10 | plugins: [
11 | typescript({
12 | check: true,
13 | clean: true,
14 | tsconfigOverride: {
15 | compilerOptions: {
16 | removeComments: true,
17 | module: "ES2015",
18 | }
19 | }
20 | }),
21 | ],
22 | external,
23 | output: [
24 | {
25 | banner,
26 | file: pkg.main,
27 | format: "cjs",
28 | },
29 | {
30 | banner,
31 | file: pkg.module,
32 | format: "es",
33 | },
34 | ],
35 | };
--------------------------------------------------------------------------------
/src/collection.ts:
--------------------------------------------------------------------------------
1 | import { ICollection } from "./types";
2 |
3 | export class Collection implements ICollection {
4 |
5 | protected items: I[] = new Array();
6 |
7 | constructor(items?: I[]) {
8 | if (items) {
9 | this.items = items;
10 | }
11 | }
12 |
13 | public get Count() {
14 | return this.items.length;
15 | }
16 |
17 | public Item(index: number): I | null {
18 | return this.items[index] || null;
19 | }
20 |
21 | public Add(item: I) {
22 | this.items.push(item);
23 | }
24 |
25 | public Pop() {
26 | return this.items.pop();
27 | }
28 |
29 | public RemoveAt(index: number) {
30 | this.items = this.items.filter((item, index2) => index2 !== index);
31 | }
32 |
33 | public Clear() {
34 | this.items = new Array();
35 | }
36 |
37 | public GetIterator() {
38 | return this.items;
39 | }
40 |
41 | public ForEach(cb: (item: I, index: number, array: I[]) => void) {
42 | this.GetIterator().forEach(cb);
43 | }
44 |
45 | public Map(cb: (item: I, index: number, array: I[]) => U) {
46 | return new Collection(this.GetIterator().map(cb));
47 | }
48 |
49 | public Filter(cb: (item: I, index: number, array: I[]) => boolean) {
50 | return new Collection(this.GetIterator().filter(cb));
51 | }
52 |
53 | public Sort(cb: (a: I, b: I) => number) {
54 | return new Collection(this.GetIterator().sort(cb));
55 | }
56 |
57 | public Every(cb: (value: I, index: number, array: I[]) => boolean) {
58 | return this.GetIterator().every(cb);
59 | }
60 |
61 | public Some(cb: (value: I, index: number, array: I[]) => boolean) {
62 | return this.GetIterator().some(cb);
63 | }
64 |
65 | public IsEmpty() {
66 | return this.Count === 0;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/const.ts:
--------------------------------------------------------------------------------
1 | export const ELEMENT = "element";
2 | export const ATTRIBUTE = "attribute";
3 | export const CONTENT = "content";
4 |
--------------------------------------------------------------------------------
/src/convert.ts:
--------------------------------------------------------------------------------
1 | import { XE, XmlError } from "./error";
2 | import { XmlBufferEncoding } from "./types";
3 |
4 | declare let unescape: any;
5 | declare let escape: any;
6 |
7 | export class Convert {
8 |
9 | public static ToString(buffer: BufferSource, enc: XmlBufferEncoding = "utf8") {
10 | const buf = new Uint8Array(buffer as ArrayBuffer);
11 | switch (enc.toLowerCase()) {
12 | case "utf8":
13 | return this.ToUtf8String(buf);
14 | case "binary":
15 | return this.ToBinary(buf);
16 | case "hex":
17 | return this.ToHex(buf);
18 | case "base64":
19 | return this.ToBase64(buf);
20 | case "base64url":
21 | return this.ToBase64Url(buf);
22 | default:
23 | throw new XmlError(XE.CONVERTER_UNSUPPORTED);
24 | }
25 | }
26 |
27 | public static FromString(str: string, enc: XmlBufferEncoding = "utf8"): Uint8Array {
28 | switch (enc.toLowerCase()) {
29 | case "utf8":
30 | return this.FromUtf8String(str);
31 | case "binary":
32 | return this.FromBinary(str);
33 | case "hex":
34 | return this.FromHex(str);
35 | case "base64":
36 | return this.FromBase64(str);
37 | case "base64url":
38 | return this.FromBase64Url(str);
39 | default:
40 | throw new XmlError(XE.CONVERTER_UNSUPPORTED);
41 | }
42 | }
43 |
44 | public static ToBase64(buf: Uint8Array): string {
45 | if (typeof btoa !== "undefined") {
46 | const binary = this.ToString(buf, "binary");
47 | return btoa(binary);
48 | } else if (typeof Buffer !== "undefined") {
49 | return Buffer.from(buf).toString("base64");
50 | } else {
51 | throw new XmlError(XE.CONVERTER_UNSUPPORTED);
52 | }
53 | }
54 |
55 | public static FromBase64(base64Text: string): Uint8Array {
56 | // Prepare string
57 | base64Text = base64Text.replace(/\n/g, "").replace(/\r/g, "").replace(/\t/g, "").replace(/\s/g, "");
58 | if (typeof atob !== "undefined") {
59 | return this.FromBinary(atob(base64Text));
60 | } else if (typeof Buffer !== "undefined") {
61 | return new Uint8Array(Buffer.from(base64Text, "base64"));
62 | } else {
63 | throw new XmlError(XE.CONVERTER_UNSUPPORTED);
64 | }
65 | }
66 |
67 | public static FromBase64Url(base64url: string): Uint8Array {
68 | return this.FromBase64(this.Base64Padding(base64url.replace(/\-/g, "+").replace(/\_/g, "/")));
69 | }
70 |
71 | public static ToBase64Url(data: Uint8Array): string {
72 | return this.ToBase64(data).replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
73 | }
74 |
75 | public static FromUtf8String(text: string): Uint8Array {
76 | const s = unescape(encodeURIComponent(text));
77 | const uintArray = new Uint8Array(s.length);
78 | for (let i = 0; i < s.length; i++) {
79 | uintArray[i] = s.charCodeAt(i);
80 | }
81 | return uintArray;
82 | }
83 | public static ToUtf8String(buffer: Uint8Array): string {
84 | const encodedString = String.fromCharCode.apply(null, buffer as any);
85 | const decodedString = decodeURIComponent(escape(encodedString));
86 | return decodedString;
87 | }
88 |
89 | public static FromBinary(text: string): Uint8Array {
90 | const stringLength = text.length;
91 | const resultView = new Uint8Array(stringLength);
92 | for (let i = 0; i < stringLength; i++) {
93 | resultView[i] = text.charCodeAt(i);
94 | }
95 | return resultView;
96 | }
97 | public static ToBinary(buffer: Uint8Array): string {
98 | let resultString = "";
99 | for (let i = 0; i < buffer.length; i++) {
100 | resultString = resultString + String.fromCharCode(buffer[i]);
101 | }
102 | return resultString;
103 | }
104 |
105 | /**
106 | * Converts buffer to HEX string
107 | * @param {BufferSource} buffer Incoming buffer
108 | * @returns string
109 | */
110 | public static ToHex(buffer: Uint8Array): string {
111 | const splitter = "";
112 | const res: string[] = [];
113 | for (let i = 0; i < buffer.length; i++) {
114 | const char = buffer[i].toString(16);
115 | res.push(char.length === 1 ? "0" + char : char);
116 | }
117 | return res.join(splitter);
118 | }
119 |
120 | /**
121 | * Converts HEX string to buffer
122 | *
123 | * @static
124 | * @param {string} hexString
125 | * @returns {Uint8Array}
126 | *
127 | * @memberOf Convert
128 | */
129 | public static FromHex(hexString: string): Uint8Array {
130 | const res = new Uint8Array(hexString.length / 2);
131 | for (let i = 0; i < hexString.length; i = i + 2) {
132 | const c = hexString.slice(i, i + 2);
133 | res[i / 2] = parseInt(c, 16);
134 | }
135 | return res;
136 | }
137 |
138 | /**
139 | * Converts string to Date
140 | *
141 | * @static
142 | * @param {string} dateTime
143 | * @returns {Date}
144 | *
145 | * @memberOf Convert
146 | */
147 | public static ToDateTime(dateTime: string): Date {
148 | return new Date(dateTime);
149 | }
150 |
151 | /**
152 | * Converts Date to string
153 | *
154 | * @static
155 | * @param {Date} dateTime
156 | * @returns {string}
157 | *
158 | * @memberOf Convert
159 | */
160 | public static FromDateTime(dateTime: Date): string {
161 | const str = dateTime.toISOString();
162 | return str;
163 | }
164 |
165 | protected static Base64Padding(base64: string): string {
166 | const padCount = 4 - (base64.length % 4);
167 | if (padCount < 4) {
168 | for (let i = 0; i < padCount; i++) {
169 | base64 += "=";
170 | }
171 | }
172 | return base64;
173 | }
174 |
175 | }
176 |
--------------------------------------------------------------------------------
/src/converters.ts:
--------------------------------------------------------------------------------
1 | import { Convert } from "./convert";
2 | import { IConverter } from "./types";
3 |
4 | export const XmlBase64Converter: IConverter = {
5 | get: (value: Uint8Array) => {
6 | if (value) {
7 | return Convert.ToBase64(value);
8 | }
9 | return void 0;
10 | },
11 | set: (value: string) => {
12 | return Convert.FromBase64(value);
13 | },
14 | };
15 |
16 | export const XmlNumberConverter: IConverter = {
17 | get: (value: number) => {
18 | if (value) {
19 | return value.toString();
20 | }
21 | return "0";
22 | },
23 | set: (value: string) => {
24 | return Number(value);
25 | },
26 | };
27 |
28 | export const XmlBooleanConverter: IConverter = {
29 | get: (value: boolean) => {
30 | if (value) {
31 | return value.toString();
32 | }
33 | return "false";
34 | },
35 | set: (value: string) => {
36 | if (value && value.toLowerCase() === "true") {
37 | return true;
38 | } else {
39 | return false;
40 | }
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/decorators.ts:
--------------------------------------------------------------------------------
1 | import * as CONST from "./const";
2 | import { XmlAttributeType, XmlChildElementType, XmlContentType, XmlElementType, XmlSchema } from "./types";
3 |
4 | const MAX = 1e9;
5 |
6 | function assign(target: any, ...sources: any[]) {
7 | const res = arguments[0];
8 | for (let i = 1; i < arguments.length; i++) {
9 | const obj = arguments[i];
10 | for (const prop in obj) {
11 | if (!obj.hasOwnProperty(prop)) {
12 | continue;
13 | }
14 | res[prop] = obj[prop];
15 | }
16 | }
17 | return res;
18 | }
19 |
20 | export function XmlElement(params: XmlElementType) {
21 | return (target: TFunction) => {
22 | const t = target as XmlSchema;
23 |
24 | t.localName = params.localName || (t as any).name;
25 | t.namespaceURI = params.namespaceURI || t.namespaceURI || null;
26 | t.prefix = params.prefix || t.prefix || null;
27 | t.parser = params.parser || t.parser;
28 | if (t.target !== t) {
29 | t.items = assign({}, t.items);
30 | }
31 | t.target = target;
32 | };
33 | }
34 |
35 | export function XmlChildElement(params: XmlChildElementType = {}) {
36 | return (target: Object, propertyKey: string | symbol) => {
37 | const t = target.constructor as XmlSchema;
38 | const key = propertyKey as string;
39 |
40 | if (!t.items) {
41 | t.items = {};
42 | }
43 |
44 | if (t.target !== t) {
45 | t.items = assign({}, t.items);
46 | }
47 | t.target = target;
48 |
49 | if (params.parser) {
50 | t.items![key] = {
51 | parser: params.parser,
52 | required: params.required || false,
53 | maxOccurs: params.maxOccurs || MAX,
54 | minOccurs: params.minOccurs === void 0 ? 0 : params.minOccurs,
55 | noRoot: params.noRoot || false,
56 | };
57 | } else {
58 | t.items![key] = {
59 | namespaceURI: params.namespaceURI || null,
60 | required: params.required || false,
61 | prefix: params.prefix || null,
62 | defaultValue: params.defaultValue,
63 | converter: params.converter,
64 | noRoot: params.noRoot || false,
65 | };
66 | }
67 | params.localName = params.localName || (params.parser && (params.parser as any).localName) || key;
68 | t.items![key].namespaceURI = params.namespaceURI || (params.parser && (params.parser as any).namespaceURI) || null;
69 | t.items![key].prefix = params.prefix || (params.parser && (params.parser as any).prefix) || null;
70 | t.items![key].localName = params.localName;
71 | t.items![key].type = CONST.ELEMENT;
72 |
73 | defineProperty(target, key, params);
74 | };
75 | }
76 |
77 | export function XmlAttribute(params: XmlAttributeType = { required: false, namespaceURI: null }) {
78 | return (target: Object, propertyKey: string) => {
79 | const t = target.constructor as XmlSchema;
80 | const key = propertyKey as string;
81 |
82 | if (!params.localName) {
83 | params.localName = propertyKey as string;
84 | }
85 |
86 | if (!t.items) {
87 | t.items = {};
88 | }
89 |
90 | if (t.target !== t) {
91 | t.items = assign({}, t.items);
92 | }
93 | t.target = target;
94 |
95 | t.items![propertyKey] = params;
96 | t.items![propertyKey].type = CONST.ATTRIBUTE;
97 |
98 | defineProperty(target, key, params);
99 | };
100 | }
101 |
102 | export function XmlContent(params: XmlContentType = { required: false }) {
103 | return (target: Object, propertyKey: string) => {
104 | const t = target.constructor as XmlSchema;
105 | const key = propertyKey as string;
106 |
107 | if (!t.items) {
108 | t.items = {};
109 | }
110 |
111 | if (t.target !== t) {
112 | t.items = assign({}, t.items);
113 | }
114 | t.target = target;
115 |
116 | t.items![propertyKey] = params;
117 | t.items![propertyKey].type = CONST.CONTENT;
118 |
119 | defineProperty(target, key, params);
120 | };
121 | }
122 |
123 | function defineProperty(target: any, key: string, params: any) {
124 | const key2 = `_${key}`;
125 |
126 | const opt = {
127 | set: function (this: any, v: any) {
128 | if (this[key2] !== v) {
129 | this.element = null;
130 | this[key2] = v;
131 | }
132 | },
133 | get: function (this: any) {
134 | if (this[key2] === void 0) {
135 | let defaultValue = params.defaultValue;
136 | if (params.parser) {
137 | defaultValue = new params.parser();
138 | defaultValue.localName = params.localName;
139 | }
140 | this[key2] = defaultValue;
141 | }
142 | return this[key2];
143 | },
144 | };
145 |
146 | // private property
147 | Object.defineProperty(target, key2, { writable: true, enumerable: false });
148 | // public property
149 | Object.defineProperty(target, key, opt);
150 | }
151 |
--------------------------------------------------------------------------------
/src/error.ts:
--------------------------------------------------------------------------------
1 | interface PrintfArgs {
2 | arg: string;
3 | index: number;
4 | }
5 |
6 | function printf(text: string, ...args: any[]) {
7 | let msg: string = text;
8 | const regFind = /[^%](%\d+)/g;
9 | let match: RegExpExecArray | null = null;
10 | const matches: PrintfArgs[] = [];
11 | while (match = regFind.exec(msg)) {
12 | matches.push({ arg: match[1], index: match.index });
13 | }
14 |
15 | // replace matches
16 | for (let i = matches.length - 1; i >= 0; i--) {
17 | const item = matches[i];
18 | const arg = item.arg.substring(1);
19 | const index = item.index + 1;
20 | msg = msg.substring(0, index) + arguments[+arg] + msg.substring(index + 1 + arg.length);
21 | }
22 |
23 | // convert %% -> %
24 | msg = msg.replace("%%", "%");
25 |
26 | return msg;
27 | }
28 |
29 | function padNum(num: number, size: number): string {
30 | let s = num + "";
31 | while (s.length < size) {
32 | s = "0" + s;
33 | }
34 | return s;
35 | }
36 |
37 | export class XmlError implements Error {
38 | public stack: any;
39 | public code: number;
40 | public name: string;
41 | public message: string;
42 | protected readonly prefix = "XMLJS";
43 | constructor(code: XE, ...args: any[]) {
44 | this.code = code;
45 | this.name = (this.constructor as any).name;
46 | arguments[0] = xes[code];
47 | const message = printf.apply(this, arguments as any);
48 | this.message = `${this.prefix}${padNum(code, 4)}: ${message}`;
49 | this.stack = (new Error(this.message) as any).stack;
50 | }
51 | }
52 |
53 | export enum XE {
54 | NONE,
55 | NULL_REFERENCE,
56 | NULL_PARAM,
57 | DECORATOR_NULL_PARAM,
58 | COLLECTION_LIMIT,
59 | METHOD_NOT_IMPLEMENTED,
60 | METHOD_NOT_SUPPORTED,
61 | PARAM_REQUIRED,
62 | CONVERTER_UNSUPPORTED,
63 | ELEMENT_MALFORMED,
64 | ELEMENT_MISSING,
65 | ATTRIBUTE_MISSING,
66 | CONTENT_MISSING,
67 | CRYPTOGRAPHIC,
68 | CRYPTOGRAPHIC_NO_MODULE,
69 | CRYPTOGRAPHIC_UNKNOWN_TRANSFORM,
70 | ALGORITHM_NOT_SUPPORTED,
71 | ALGORITHM_WRONG_NAME,
72 | XML_EXCEPTION,
73 | }
74 |
75 | interface IXmlError {
76 | [index: number]: string;
77 | }
78 |
79 | const xes: IXmlError = {};
80 | xes[XE.NONE] = "No description";
81 | xes[XE.NULL_REFERENCE] = "Null reference";
82 | xes[XE.NULL_PARAM] = "'%1' has empty '%2' object";
83 | xes[XE.DECORATOR_NULL_PARAM] = "Decorator '%1' has empty '%2' parameter";
84 | xes[XE.COLLECTION_LIMIT] = "Collection of '%1' in element '%2' has wrong amount of items";
85 | xes[XE.METHOD_NOT_IMPLEMENTED] = "Method is not implemented";
86 | xes[XE.METHOD_NOT_SUPPORTED] = "Method is not supported";
87 | xes[XE.PARAM_REQUIRED] = "Required parameter is missing '%1'";
88 | xes[XE.CONVERTER_UNSUPPORTED] = "Converter is not supported";
89 | xes[XE.ELEMENT_MALFORMED] = "Malformed element '%1'";
90 | xes[XE.ELEMENT_MISSING] = "Element '%1' is missing in '%2'";
91 | xes[XE.ATTRIBUTE_MISSING] = "Attribute '%1' is missing in '%2'";
92 | xes[XE.CONTENT_MISSING] = "Content is missing in '%1'";
93 | xes[XE.CRYPTOGRAPHIC] = "Cryptographic error: %1";
94 | xes[XE.CRYPTOGRAPHIC_NO_MODULE] = "WebCrypto module is not found";
95 | xes[XE.CRYPTOGRAPHIC_UNKNOWN_TRANSFORM] = "Unknown transform %1";
96 | xes[XE.ALGORITHM_NOT_SUPPORTED] = "Algorithm is not supported '%1'";
97 | xes[XE.ALGORITHM_WRONG_NAME] = "Algorithm wrong name in use '%1'";
98 | xes[XE.XML_EXCEPTION] = "XML exception: %1";
99 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types";
2 | export * from "./decorators";
3 | export * from "./collection";
4 | export * from "./convert";
5 | export * from "./utils";
6 | export * from "./converters";
7 | export * from "./error";
8 | export * from "./xml";
9 | export * from "./xml_collection";
10 | export * from "./xml_object";
11 | export * from "./namespace_manager";
12 |
--------------------------------------------------------------------------------
/src/namespace_manager.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from "./collection";
2 | import { XmlNamespace } from "./types";
3 |
4 | export class NamespaceManager extends Collection {
5 |
6 | public Add(item: XmlNamespace) {
7 | item.prefix = item.prefix || "";
8 | item.namespace = item.namespace || "";
9 | super.Add(item);
10 | }
11 |
12 | public GetPrefix(prefix: string, start: number = this.Count - 1): XmlNamespace | null {
13 | const lim = this.Count - 1;
14 | prefix = prefix || "";
15 | if (start > lim) {
16 | start = lim;
17 | }
18 | for (let i = start; i >= 0; i--) {
19 | const item = this.items[i];
20 | if (item.prefix === prefix) {
21 | return item;
22 | }
23 | }
24 | return null;
25 | }
26 |
27 | public GetNamespace(namespaceUrl: string, start: number = this.Count - 1): XmlNamespace | null {
28 | const lim = this.Count - 1;
29 | namespaceUrl = namespaceUrl || "";
30 | if (start > lim) {
31 | start = lim;
32 | }
33 | for (let i = start; i >= 0; i--) {
34 | const item = this.items[i];
35 | if (item.namespace === namespaceUrl) {
36 | return item;
37 | }
38 | }
39 | return null;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base interface for collections
3 | *
4 | * @interface ICollection
5 | * @template I
6 | */
7 | export interface ICollection {
8 | readonly Count: number;
9 | Item(index: number): I | null;
10 | Add(item: I): void;
11 | Pop(): I | undefined;
12 | RemoveAt(index: number): void;
13 | Clear(): void;
14 | GetIterator(): I[];
15 | ForEach(cb: (item: I, index: number, array: I[]) => void): void;
16 | Map(cb: (item: I, index: number, array: I[]) => U): ICollection;
17 | Filter(cb: (item: I, index: number, array: I[]) => boolean): ICollection;
18 | Sort(cb: (a: I, b: I) => number): ICollection;
19 | Every(cb: (value: I, index: number, array: I[]) => boolean): boolean;
20 | Some(cb: (value: I, index: number, array: I[]) => boolean): boolean;
21 | IsEmpty(): boolean;
22 | }
23 |
24 | export interface IXmlSerializable {
25 |
26 | /**
27 | * Writes object to XML node
28 | * - if class was initialized and it has no one change, GetXml returns null
29 | * @returns Node
30 | */
31 | GetXml(): Node | null;
32 | /**
33 | * Reads XML from string
34 | * @param {Node} node
35 | * @returns void
36 | */
37 | LoadXml(node: Node | string): void;
38 | }
39 |
40 | export type IXmlSerializableConstructor = new () => IXmlSerializable;
41 |
42 | /**
43 | * Base type for associated arrays
44 | *
45 | * @interface AssocArray
46 | * @template T
47 | */
48 | export interface AssocArray {
49 | [index: string]: T;
50 | }
51 |
52 | export declare type XmlBufferEncoding = string | "utf8" | "binary" | "hex" | "base64" | "base64url";
53 |
54 | export declare type ISelectResult = Node[] | Node | boolean | number | string;
55 |
56 | export interface XmlNamespace {
57 | /**
58 | * Prefix
59 | *
60 | * @type {(string |)}
61 | * @memberOf XmlNamespace
62 | */
63 | prefix: string | null;
64 | /**
65 | * Namespace URI
66 | *
67 | * @type {(string |)}
68 | * @memberOf XmlNamespace
69 | */
70 | namespace: string | null;
71 | }
72 |
73 | export interface XmlSchemaItemBase {
74 | /**
75 | * Local name of item
76 | *
77 | * @type {string}
78 | * @memberOf XmlSchemaItemBase
79 | */
80 | localName?: string;
81 | /**
82 | * Namespace URI of attribute
83 | *
84 | * @type {(string |)}
85 | * @memberOf XmlSchemaItemBase
86 | */
87 | namespaceURI?: string | null;
88 |
89 | /**
90 | * Default prefix for Xml element
91 | *
92 | * @type {(string |)}
93 | * @memberOf XmlSchemaItemBase
94 | */
95 | prefix?: string | null;
96 | }
97 |
98 | export interface XmlSchemaItem extends XmlSchemaItemBase {
99 | /**
100 | * Default value for item
101 | *
102 | * @type {(T |)}
103 | * @memberOf XmlSchemaItem
104 | */
105 | defaultValue?: T | null;
106 | /**
107 | * Determine where item is required
108 | *
109 | * @type {boolean}
110 | * @memberOf XmlSchemaItem
111 | */
112 | required?: boolean;
113 | /**
114 | * Custom converter for item value
115 | *
116 | * @type {IConverter}
117 | * @memberOf XmlAttributeType
118 | */
119 | converter?: IConverter;
120 | }
121 |
122 | export interface XmlSchemaItemParser {
123 | /**
124 | * Xml parser for item
125 | *
126 | * @type {*}
127 | * @memberOf XmlSchemaItemParser
128 | */
129 | parser?: IXmlSerializableConstructor;
130 | }
131 |
132 | export interface XmlAttributeType extends XmlSchemaItem {
133 | }
134 |
135 | export interface XmlContentType {
136 | /**
137 | * Default value for item
138 | *
139 | * @type {(T |)}
140 | * @memberOf XmlContentType
141 | */
142 | defaultValue?: T | null;
143 | /**
144 | * Determine where item is required
145 | *
146 | * @type {boolean}
147 | * @memberOf XmlContentType
148 | */
149 | required?: boolean;
150 | /**
151 | * Custom converter for item value
152 | *
153 | * @type {IConverter}
154 | * @memberOf XmlContentType
155 | */
156 | converter?: IConverter;
157 | }
158 |
159 | export interface XmlElementType extends XmlSchemaItemBase, XmlSchemaItemParser {
160 | /**
161 | * Local name for Xml element
162 | *
163 | * @type {string}
164 | * @memberOf XmlElementType
165 | */
166 | localName: string;
167 | /**
168 | * Namespace URI fro Xml element
169 | *
170 | * @type {(string |)}
171 | * @memberOf XmlElementType
172 | */
173 | namespaceURI?: string | null;
174 | }
175 |
176 | export interface XmlChildElementType extends XmlSchemaItem, XmlSchemaItemParser {
177 | /**
178 | * max occurs of items in collection
179 | *
180 | * @type {number}
181 | * @memberOf XmlChildElementType
182 | */
183 | maxOccurs?: number;
184 | /**
185 | * min occurs of items in collection
186 | *
187 | * @type {number}
188 | * @memberOf XmlChildElementType
189 | */
190 | minOccurs?: number;
191 | /**
192 | * Don't add root element of XmlCollection to compiled element
193 | *
194 | * @type {boolean}
195 | * @memberOf XmlChildElementType
196 | */
197 | noRoot?: boolean;
198 | }
199 |
200 | export interface XmlSchema {
201 | localName?: string;
202 | namespaceURI?: string | null;
203 | prefix?: string | null;
204 | parser?: IXmlSerializableConstructor;
205 | items?: { [key: string]: (XmlChildElementType | XmlAttributeType) & { type?: string } };
206 | target?: any;
207 | }
208 |
209 | export interface IConverter {
210 | /**
211 | * Converts value from Xml element to Object
212 | *
213 | * @memberOf IConverter
214 | */
215 | set: (value: string) => T;
216 | /**
217 | * Converts value from Object to Xml element
218 | *
219 | * @memberOf IConverter
220 | */
221 | get: (value: T) => string | undefined;
222 | }
223 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { AssocArray } from "./types";
2 | import { APPLICATION_XML, XmlNodeType } from "./xml";
3 |
4 | export type SelectNodes = (node: Node, xPath: string) => Node[];
5 |
6 | let xpath: SelectNodes = (node: Node, xPath: string) => {
7 | throw new Error("Not implemented");
8 | };
9 |
10 | // Fix global
11 | let sWindow: any;
12 | if (typeof self === "undefined") {
13 | sWindow = global;
14 | const xmldom = require("@xmldom/xmldom");
15 | xpath = require("xpath.js");
16 | sWindow.XMLSerializer = xmldom.XMLSerializer;
17 | sWindow.DOMParser = xmldom.DOMParser;
18 | sWindow.DOMImplementation = xmldom.DOMImplementation;
19 | sWindow.document = new DOMImplementation().createDocument("http://www.w3.org/1999/xhtml", "html", null!);
20 | } else {
21 | sWindow = self;
22 | }
23 |
24 | function SelectNodesEx(node: Node, xPath: string): Node[] {
25 | const doc: Document = node.ownerDocument == null ? node as Document : node.ownerDocument;
26 | const nsResolver = document.createNSResolver(node.ownerDocument == null ? (node as Document).documentElement : node.ownerDocument.documentElement);
27 | const personIterator = doc.evaluate(xPath, node, nsResolver, XPathResult.ANY_TYPE, null!);
28 | const ns: Node[] = [];
29 | let n: Node | null;
30 | while (n = personIterator.iterateNext()) {
31 | ns.push(n);
32 | }
33 | return ns;
34 | }
35 |
36 | export const Select: SelectNodes = (typeof self !== "undefined") ? SelectNodesEx : xpath;
37 |
38 | export function Parse(xmlString: string) {
39 | /**
40 | * NOTE: https://www.w3.org/TR/REC-xml/#sec-line-ends
41 | * The XML processor must behave as if it normalized all line breaks in external parsed
42 | * entities (including the document entity) on input, before parsing, by translating both
43 | * the two-character sequence #xD #xA and any #xD that is not followed by #xA to a single #xA character.
44 | */
45 | xmlString = xmlString
46 | .replace(/\r\n/g, "\n")
47 | .replace(/\r/g, "\n");
48 | return new DOMParser().parseFromString(xmlString, APPLICATION_XML);
49 | }
50 |
51 | export function Stringify(target: Node) {
52 | return new XMLSerializer().serializeToString(target);
53 | }
54 |
55 | /**
56 | * Returns single Node from given Node
57 | *
58 | * @export
59 | * @param {Node} node
60 | * @param {string} path
61 | * @returns
62 | */
63 | export function SelectSingleNode(node: Node, path: string) {
64 | const ns = Select(node, path);
65 | if (ns && ns.length > 0) {
66 | return ns[0];
67 | }
68 | return null;
69 | }
70 |
71 | function _SelectNamespaces(node: Node, selectedNodes: AssocArray = {}) {
72 | if (isElement(node)) {
73 | if (node.namespaceURI && node.namespaceURI !== "http://www.w3.org/XML/1998/namespace" && !selectedNodes[node.prefix || ""]) {
74 | selectedNodes[node.prefix ? node.prefix : ""] = node.namespaceURI!;
75 | }
76 | for (let i = 0; i < node.childNodes.length; i++) {
77 | const childNode = node.childNodes.item(i);
78 | if (childNode && childNode.nodeType === XmlNodeType.Element) {
79 | _SelectNamespaces(childNode, selectedNodes);
80 | }
81 | }
82 | }
83 | }
84 |
85 | export function SelectNamespaces(node: Element) {
86 | const attrs: AssocArray = {};
87 | _SelectNamespaces(node, attrs);
88 | return attrs;
89 | }
90 |
91 | export function assign(target: any, ...sources: any[]) {
92 | const res = arguments[0];
93 | for (let i = 1; i < arguments.length; i++) {
94 | const obj = arguments[i];
95 | for (const prop in obj) {
96 | if (!obj.hasOwnProperty(prop)) {
97 | continue;
98 | }
99 | res[prop] = obj[prop];
100 | }
101 | }
102 | return res;
103 | }
104 |
105 | /**
106 | * Returns true if object is a XML of specified type
107 | * @param obj Object to test
108 | * @param type XML Node type
109 | */
110 | function isNodeType(obj: any, type: XmlNodeType) {
111 | return obj && obj.nodeType === type;
112 | }
113 |
114 | /**
115 | * Returns true if object is a XML Element
116 | * @param obj Object to test
117 | */
118 | export function isElement(obj: any): obj is Element {
119 | return isNodeType(obj, XmlNodeType.Element);
120 | }
121 |
122 | /**
123 | * Returns true if object is a XML Document
124 | * @param obj Object to test
125 | */
126 | export function isDocument(obj: any): obj is Document {
127 | return isNodeType(obj, XmlNodeType.Document);
128 | }
129 |
--------------------------------------------------------------------------------
/src/xml.ts:
--------------------------------------------------------------------------------
1 | export const APPLICATION_XML = "application/xml";
2 | export const DEFAULT_PREFIX = "";
3 | export const DEFAULT_NAMESPACE_URI = "";
4 |
5 | export enum XmlNodeType {
6 | None = 0, // Read method has not been
7 | Element = 1,
8 | Attribute = 2,
9 | Text = 3, // The text content of a node.
10 | CDATA = 4, // For example,
11 | EntityReference = 5, // A reference to an entity
12 | Entity = 6, // For example,
13 | ProcessingInstruction = 7, // For example,
14 | Comment = 8,
15 | Document = 9, // A document object that, as the root of the document tree,
16 | // provides access to the entire XML document.
17 | DocumentType = 10, // For example,
18 | DocumentFragment = 11,
19 | Notation = 12, // A For example,
20 | Whitespace = 13, // White space between markup.
21 | SignificantWhitespace = 14, // White space between markup in a mixed content model
22 | // or white space within the xml:space="preserve" scope.
23 | EndElement = 15, // An end element tag (for example, ).
24 | EndEntity = 16, // Returned when XmlReader gets to the end of the entity
25 | // replacement as a result of a call to XmlReader.ResolveEntity()
26 | XmlDeclaration = 17, // for example,
27 | }
28 |
--------------------------------------------------------------------------------
/src/xml_collection.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from "./collection";
2 | import { XE, XmlError } from "./error";
3 | import { ICollection } from "./types";
4 | import { isElement } from "./utils";
5 | import { XmlObject } from "./xml_object";
6 |
7 | export class XmlCollection extends XmlObject implements ICollection {
8 |
9 | public static parser: any;
10 |
11 | /**
12 | * The maximum number of elements
13 | */
14 | public MaxOccurs = Number.MAX_VALUE;
15 |
16 | /**
17 | * The minimum number of elements
18 | */
19 | public MinOccurs = 0;
20 |
21 | // Collection
22 | protected items: I[] = [];
23 |
24 | public HasChanged() {
25 | const res = super.HasChanged();
26 |
27 | const changed = this.Some((item) => item.HasChanged());
28 |
29 | return res || changed;
30 | }
31 |
32 | public get Count() {
33 | return this.items.length;
34 | }
35 |
36 | public Item(index: number): I | null {
37 | return this.items[index] || null;
38 | }
39 |
40 | public Add(item: I) {
41 | this.items.push(item);
42 | this.element = null;
43 | }
44 |
45 | public Pop() {
46 | this.element = null;
47 | return this.items.pop();
48 | }
49 |
50 | public RemoveAt(index: number) {
51 | this.items = this.items.filter((item, index2) => index2 !== index);
52 | this.element = null;
53 | }
54 |
55 | public Clear() {
56 | this.items = new Array();
57 | this.element = null;
58 | }
59 |
60 | public GetIterator() {
61 | return this.items;
62 | }
63 |
64 | public ForEach(cb: (item: I, index: number, array: I[]) => void) {
65 | this.GetIterator().forEach(cb);
66 | }
67 |
68 | public Map(cb: (item: I, index: number, array: I[]) => U) {
69 | return new Collection(this.GetIterator().map(cb));
70 | }
71 |
72 | public Filter(cb: (item: I, index: number, array: I[]) => boolean) {
73 | return new Collection(this.GetIterator().filter(cb));
74 | }
75 |
76 | public Sort(cb: (a: I, b: I) => number) {
77 | return new Collection(this.GetIterator().sort(cb));
78 | }
79 |
80 | public Every(cb: (value: I, index: number, array: I[]) => boolean) {
81 | return this.GetIterator().every(cb);
82 | }
83 |
84 | public Some(cb: (value: I, index: number, array: I[]) => boolean) {
85 | return this.GetIterator().some(cb);
86 | }
87 |
88 | public IsEmpty() {
89 | return this.Count === 0;
90 | }
91 |
92 | protected OnGetXml(element: Element) {
93 | for (const item of this.GetIterator()) {
94 | const el = item.GetXml();
95 | if (el) {
96 | element.appendChild(el);
97 | }
98 | }
99 | }
100 |
101 | protected OnLoadXml(element: Element) {
102 | const self = this.GetStatic();
103 | if (!self.parser) {
104 | throw new XmlError(XE.XML_EXCEPTION, `${self.localName} doesn't have required 'parser' in @XmlElement`);
105 | }
106 | for (let i = 0; i < element.childNodes.length; i++) {
107 | const node = element.childNodes.item(i);
108 | if (!(isElement(node) &&
109 | node.localName === (self.parser as any).localName &&
110 | // tslint:disable-next-line:triple-equals
111 | node.namespaceURI == self.namespaceURI)) {
112 | // Ignore wrong elements
113 | continue;
114 | }
115 | const el = node as Element;
116 |
117 | const item = new self.parser();
118 | item.LoadXml(el);
119 | this.Add(item as any);
120 | }
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/xml_object.ts:
--------------------------------------------------------------------------------
1 | import * as CONST from "./const";
2 | import { XE, XmlError } from "./error";
3 | import { AssocArray, IXmlSerializable, XmlAttributeType, XmlChildElementType, XmlContentType, XmlSchema } from "./types";
4 | import { isDocument, isElement, Parse, SelectSingleNode } from "./utils";
5 | import { APPLICATION_XML } from "./xml";
6 |
7 | const DEFAULT_ROOT_NAME = "xml_root";
8 |
9 | export class XmlObject implements IXmlSerializable {
10 |
11 | public static LoadXml(this: new () => T, param: string | Element) {
12 | const xml = new this();
13 | xml.LoadXml(param);
14 | return xml;
15 | }
16 |
17 | public static GetElement(element: Element, name: string, required: boolean = true) {
18 | const xmlNodeList = element.getElementsByTagName(name);
19 | if (required && xmlNodeList.length === 0) {
20 | throw new XmlError(XE.ELEMENT_MISSING, name, element.localName);
21 | }
22 | return xmlNodeList[0] || null;
23 | }
24 |
25 | public static GetAttribute(element: Element, attrName: string, defaultValue: string | null, required: boolean = true) {
26 | if (element.hasAttribute(attrName)) {
27 | return element.getAttribute(attrName);
28 | } else {
29 | if (required) {
30 | throw new XmlError(XE.ATTRIBUTE_MISSING, attrName, element.localName);
31 | }
32 | return defaultValue;
33 | }
34 | }
35 |
36 | public static GetElementById(element: Document | Element, idValue: string): Element | null;
37 | public static GetElementById(node: Node, idValue: string) {
38 | if ((node == null) || (idValue == null)) {
39 | return null;
40 | }
41 |
42 | // this works only if there's a DTD or XSD available to define the ID
43 | let xel: Node | null = null;
44 | if (isDocument(node)) {
45 | xel = node.getElementById(idValue);
46 | }
47 | if (xel == null) {
48 | // search an "undefined" ID
49 | xel = SelectSingleNode(node, `//*[@*[local-name()='Id']='${idValue}']`);
50 | if (xel == null) {
51 | xel = SelectSingleNode(node, `//*[@*[local-name()='ID']='${idValue}']`);
52 | if (xel == null) {
53 | xel = SelectSingleNode(node, `//*[@*[local-name()='id']='${idValue}']`);
54 | }
55 | }
56 | }
57 | return xel as Element;
58 | }
59 |
60 | /**
61 | * Creates new instance of XmlDocument with given name of root element
62 | * @param {string} root Name of root element
63 | * @param {string} namespaceUri
64 | * @param {string} prefix
65 | * @returns Document
66 | */
67 | public static CreateDocument(root: string = DEFAULT_ROOT_NAME, namespaceUri: string | null = null, prefix: string | null = null): Document {
68 | let namePrefix = "";
69 | let nsPrefix = "";
70 | let namespaceUri2 = "";
71 | if (prefix) {
72 | namePrefix = prefix + ":";
73 | nsPrefix = ":" + prefix;
74 | }
75 | if (namespaceUri) {
76 | namespaceUri2 = ` xmlns${nsPrefix}="${namespaceUri}"`;
77 | }
78 | const name = `${namePrefix}${root}`;
79 | const doc = new DOMParser().parseFromString(`<${name}${namespaceUri2}>${name}>`, APPLICATION_XML);
80 | return doc;
81 | }
82 |
83 | public static GetChildren(node: Node, localName: string, nameSpace?: string): Element[] {
84 | node = isDocument(node) ? node.documentElement : node;
85 | const res: Element[] = [];
86 | for (let i = 0; i < node.childNodes.length; i++) {
87 | const child = node.childNodes[i];
88 | if (isElement(child) && child.localName === localName && (child.namespaceURI === nameSpace || !nameSpace)) {
89 | res.push(child);
90 | }
91 | }
92 | return res;
93 | }
94 |
95 | public static GetFirstChild(node: Node, localName: string, nameSpace?: string): Element | null {
96 | node = isDocument(node) ? node.documentElement : node;
97 | for (let i = 0; i < node.childNodes.length; i++) {
98 | const child = node.childNodes[i];
99 | if (isElement(child) && child.localName === localName && (child.namespaceURI === nameSpace || !nameSpace)) {
100 | return child;
101 | }
102 | }
103 | return null;
104 | }
105 | public static GetChild(node: Element, localName: string, nameSpace?: string, required = true): Element | null {
106 | for (let i = 0; i < node.childNodes.length; i++) {
107 | const child = node.childNodes[i];
108 | if (isElement(child) && child.localName === localName && (child.namespaceURI === nameSpace || !nameSpace)) {
109 | return child;
110 | }
111 | }
112 | if (required) {
113 | throw new XmlError(XE.ELEMENT_MISSING, localName, node.localName);
114 | }
115 | return null;
116 | }
117 |
118 | protected static attributes: AssocArray>;
119 | protected static elements: AssocArray>;
120 | protected static prefix: string | null;
121 | protected static namespaceURI: string | null;
122 | protected static localName: string;
123 |
124 | /**
125 | * XmlElement
126 | * undefined - class initialized
127 | * null - has some changes
128 | * element - has cached element
129 | *
130 | * @protected
131 | * @type {(Element | null | undefined)}
132 | * @memberOf XmlObject
133 | */
134 | protected element?: Element | null;
135 | protected prefix = this.GetStatic().prefix || null;
136 |
137 | protected localName = this.GetStatic().localName;
138 | protected namespaceURI = this.GetStatic().namespaceURI;
139 |
140 | constructor (properties: Object = {}) {
141 | if (properties) {
142 | for (const [key, value] of Object.entries(properties)) {
143 | if (value !== undefined) {
144 | (this as any)[key] = value;
145 | }
146 | }
147 | }
148 | }
149 |
150 | public get Element() {
151 | return this.element;
152 | }
153 |
154 | public get Prefix() {
155 | return this.prefix;
156 | }
157 | public set Prefix(value: string | null) {
158 | this.prefix = value;
159 | }
160 |
161 | public get LocalName(): string {
162 | return this.localName!;
163 | }
164 | public get NamespaceURI(): string | null {
165 | return this.namespaceURI || null;
166 | }
167 |
168 | public HasChanged() {
169 | const self = this.GetStatic();
170 |
171 | // Check changed elements
172 | if (self.items) {
173 | for (const key in self.items) {
174 | if (!self.items.hasOwnProperty(key)) {
175 | continue;
176 | }
177 | const item: XmlChildElementType = self.items[key];
178 | const value = (this as any)[key];
179 |
180 | if (item.parser && value && value.HasChanged()) {
181 | return true;
182 | }
183 | }
184 | }
185 | return this.element === null;
186 | }
187 |
188 | public GetXml(hard?: boolean): Element | null {
189 | if (!(hard || this.HasChanged())) {
190 | return this.element || null;
191 | }
192 |
193 | const thisAny = this as any;
194 | const doc = this.CreateDocument();
195 | const el = this.CreateElement();
196 | const self = this.GetStatic();
197 |
198 | const localName: string = this.localName!;
199 |
200 | // Add attributes
201 | if (self.items) {
202 | for (const key in self.items) {
203 | if (!self.items.hasOwnProperty(key)) {
204 | continue;
205 | }
206 | const parser = thisAny[key];
207 | const selfItem: any = self.items[key];
208 | switch (selfItem.type) {
209 | case CONST.CONTENT: {
210 | const schema: XmlContentType = selfItem;
211 | const value = (schema.converter) ? schema.converter.get(parser) : parser;
212 | if (schema.required && (value === null || value === void 0)) {
213 | throw new XmlError(XE.CONTENT_MISSING, localName);
214 | }
215 |
216 | if (schema.defaultValue !== parser || schema.required) {
217 | el.textContent = value;
218 | }
219 | break;
220 | }
221 | case CONST.ATTRIBUTE: {
222 | const schema: XmlAttributeType = selfItem;
223 | const value = (schema.converter) ? schema.converter.get(parser) : parser;
224 | if (schema.required && (value === null || value === void 0)) {
225 | throw new XmlError(XE.ATTRIBUTE_MISSING, schema.localName, localName);
226 | }
227 |
228 | // attr value
229 | if (schema.defaultValue !== parser || schema.required) {
230 | if (!schema.namespaceURI) {
231 | el.setAttribute(schema.localName!, value);
232 | } else {
233 | el.setAttributeNS(schema.namespaceURI, schema.localName!, value);
234 | }
235 | }
236 | break;
237 | }
238 | case CONST.ELEMENT: {
239 | // Add elements
240 | const schema = selfItem as XmlChildElementType;
241 | let node: Element | null = null;
242 |
243 | if (schema.parser) {
244 | if ((schema.required && !parser) ||
245 | (schema.minOccurs && !parser.Count)) {
246 | throw new XmlError(XE.ELEMENT_MISSING, parser.localName, localName);
247 | }
248 |
249 | if (parser) {
250 | node = parser.GetXml(parser.element === void 0 && (schema.required || parser.Count));
251 | }
252 | } else {
253 | const value = (schema.converter) ? schema.converter.get(parser) : parser;
254 | if (schema.required && value === void 0) {
255 | throw new XmlError(XE.ELEMENT_MISSING, schema.localName, localName);
256 | }
257 | if (parser !== schema.defaultValue || schema.required) {
258 | if (!schema.namespaceURI) {
259 | node = doc.createElement(`${schema.prefix ? schema.prefix + ":" : ""}${schema.localName}`);
260 | } else {
261 | node = doc.createElementNS(schema.namespaceURI, `${schema.prefix ? schema.prefix + ":" : ""}${schema.localName}`);
262 | }
263 | if (Array.isArray(value)) {
264 | for (const child of value) {
265 | const val = child instanceof XmlObject ?
266 | child.GetXml(true) :
267 | child;
268 | if (val !== null) {
269 | node!.appendChild(val);
270 | }
271 | }
272 | } else if (value instanceof XmlObject) {
273 | node!.appendChild(value.GetXml(true) as Element);
274 | } else {
275 | node!.textContent = value;
276 | }
277 | }
278 | }
279 |
280 | if (node) {
281 | if (schema.noRoot) {
282 | const els: Element[] = [];
283 | // no root
284 | for (let i = 0; i < node.childNodes.length; i++) {
285 | const colNode = node.childNodes.item(i);
286 | if (isElement(colNode)) {
287 | els.push(colNode);
288 | }
289 | }
290 | if (els.length < schema.minOccurs! || els.length > schema.maxOccurs!) {
291 | throw new XmlError(XE.COLLECTION_LIMIT, parser.localName, self.localName);
292 | }
293 | els.forEach((e) => el.appendChild(e.cloneNode(true)));
294 | } else if (node.childNodes.length < schema.minOccurs! || node.childNodes.length > schema.maxOccurs!) {
295 | throw new XmlError(XE.COLLECTION_LIMIT, parser.localName, self.localName);
296 | } else {
297 | el.appendChild(node);
298 | }
299 | }
300 | break;
301 | }
302 | }
303 | }
304 | }
305 |
306 | // Set custom
307 | this.OnGetXml(el);
308 |
309 | // Cache compiled elements
310 | this.element = el;
311 | return el;
312 | }
313 |
314 | public LoadXml(param: string | Element) {
315 | let element: Element;
316 | const thisAny = this as any;
317 | if (typeof param === "string") {
318 | const doc = Parse(param);
319 | element = doc.documentElement;
320 | } else {
321 | element = param;
322 | }
323 |
324 | if (!element) {
325 | throw new XmlError(XE.PARAM_REQUIRED, "element");
326 | }
327 |
328 | const self = this.GetStatic();
329 | const localName = this.localName!;
330 |
331 | // tslint:disable-next-line:triple-equals
332 | if (!((element.localName === localName) && (element.namespaceURI == this.NamespaceURI))) {
333 | throw new XmlError(XE.ELEMENT_MALFORMED, localName);
334 | }
335 |
336 | // Get attributes
337 | if (self.items) {
338 | for (const key in self.items) {
339 | if (!self.items.hasOwnProperty(key)) {
340 | continue;
341 | }
342 | const selfItem: any = self.items[key];
343 | switch (selfItem.type) {
344 | case CONST.CONTENT: {
345 | const schema: XmlContentType = selfItem;
346 |
347 | if (schema.required && !element.textContent) {
348 | throw new XmlError(XE.CONTENT_MISSING, localName);
349 | }
350 |
351 | if (!element.textContent) {
352 | thisAny[key] = schema.defaultValue;
353 | } else {
354 | const value = schema.converter ? schema.converter.set(element.textContent) : element.textContent;
355 | thisAny[key] = value;
356 | }
357 | break;
358 | }
359 | case CONST.ATTRIBUTE: {
360 | const schema: XmlAttributeType = selfItem;
361 |
362 | let hasAttribute: () => boolean;
363 | let getAttribute: () => string | null;
364 |
365 | if (!schema.localName) {
366 | throw new XmlError(XE.PARAM_REQUIRED, "localName");
367 | }
368 |
369 | if (schema.namespaceURI) {
370 | hasAttribute = element.hasAttributeNS.bind(element, schema.namespaceURI, schema.localName);
371 | getAttribute = element.getAttributeNS.bind(element, schema.namespaceURI, schema.localName);
372 | } else {
373 | hasAttribute = element.hasAttribute.bind(element, schema.localName);
374 | getAttribute = element.getAttribute.bind(element, schema.localName);
375 | }
376 |
377 | if (schema.required && !hasAttribute()) {
378 | throw new XmlError(XE.ATTRIBUTE_MISSING, schema.localName, localName);
379 | }
380 |
381 | if (!hasAttribute()) {
382 | thisAny[key] = schema.defaultValue;
383 | } else {
384 | const value = schema.converter ? schema.converter.set(getAttribute()!) : getAttribute()!;
385 | thisAny[key] = value;
386 | }
387 | break;
388 | }
389 | case CONST.ELEMENT: {
390 | // Get element
391 | const schema: XmlChildElementType = selfItem;
392 | // noRoot
393 | if (schema.noRoot) {
394 | if (!schema.parser) {
395 | throw new XmlError(XE.XML_EXCEPTION, `Schema for '${schema.localName}' with flag noRoot must have 'parser'`);
396 | }
397 | const col: XmlCollection = new schema.parser() as any;
398 | if (!(col instanceof XmlCollection)) {
399 | throw new XmlError(XE.XML_EXCEPTION, `Schema for '${schema.localName}' with flag noRoot must have 'parser' like instance of XmlCollection`);
400 | }
401 | (col as any).OnLoadXml(element); // protected member
402 | delete col.element; // reset cache status
403 |
404 | if (col.Count < schema.minOccurs! || col.Count > schema.maxOccurs!) {
405 | throw new XmlError(XE.COLLECTION_LIMIT, (schema.parser as any).localName, localName);
406 | }
407 | thisAny[key] = col;
408 | continue;
409 | }
410 |
411 | // Get element by localName
412 | let foundElement: Element | null = null;
413 | for (let i = 0; i < element.childNodes.length; i++) {
414 | const node = element.childNodes.item(i);
415 | if (!isElement(node)) {
416 | continue;
417 | }
418 | const el = node;
419 | if (el.localName === schema.localName &&
420 | // tslint:disable-next-line:triple-equals
421 | el.namespaceURI == schema.namespaceURI) {
422 | foundElement = el;
423 | break;
424 | }
425 | }
426 |
427 | // required
428 | if (schema.required && !foundElement) {
429 | throw new XmlError(XE.ELEMENT_MISSING, schema.parser ? (schema.parser as any).localName : schema.localName, localName);
430 | }
431 |
432 | if (!schema.parser) {
433 |
434 | // simple element
435 | if (!foundElement) {
436 | thisAny[key] = schema.defaultValue;
437 | } else {
438 | const value = schema.converter ? schema.converter.set(foundElement.textContent!) : foundElement.textContent;
439 | thisAny[key] = value;
440 | }
441 | } else {
442 | // element
443 | if (foundElement) {
444 | const value = new schema.parser() as IXmlSerializable;
445 | (value as any).localName = schema.localName;
446 | (value as any).namespaceURI = schema.namespaceURI;
447 | thisAny[key] = value;
448 | value.LoadXml(foundElement);
449 | }
450 | }
451 | break;
452 | }
453 | }
454 | }
455 | }
456 |
457 | // Get custom
458 | this.OnLoadXml(element);
459 |
460 | this.prefix = element.prefix || "";
461 | this.element = element;
462 | }
463 |
464 | /**
465 | * Returns current Xml as string
466 | * - if element was initialized without changes, returns empty string
467 | */
468 | public toString(): string {
469 | const xml = this.GetXml();
470 | return xml ? new XMLSerializer().serializeToString(xml) : "";
471 | }
472 |
473 | public GetElement(name: string, required: boolean = true) {
474 | if (!this.element) {
475 | throw new XmlError(XE.NULL_PARAM, this.localName);
476 | }
477 | return XmlObject.GetElement(this.element, name, required);
478 | }
479 |
480 | public GetChildren(localName: string, nameSpace?: string) {
481 | if (!this.element) {
482 | throw new XmlError(XE.NULL_PARAM, this.localName);
483 | }
484 | return XmlObject.GetChildren(this.element, localName, nameSpace || this.NamespaceURI || undefined);
485 | }
486 |
487 | public GetChild(localName: string, required = true): Element | null {
488 | if (!this.element) {
489 | throw new XmlError(XE.NULL_PARAM, this.localName);
490 | }
491 | return XmlObject.GetChild(this.element, localName, this.NamespaceURI || undefined, required);
492 | }
493 |
494 | public GetFirstChild(localName: string, namespace?: string) {
495 | if (!this.element) {
496 | throw new XmlError(XE.NULL_PARAM, this.localName);
497 | }
498 | return XmlObject.GetFirstChild(this.element, localName, namespace);
499 | }
500 |
501 | public GetAttribute(name: string, defaultValue: string | null, required: boolean = true) {
502 | if (!this.element) {
503 | throw new XmlError(XE.NULL_PARAM, this.localName);
504 | }
505 | return XmlObject.GetAttribute(this.element, name, defaultValue, required);
506 | }
507 |
508 | public IsEmpty() {
509 | return this.Element === void 0;
510 | }
511 |
512 | protected OnLoadXml(element: Element) {
513 | // Empty
514 | }
515 |
516 | protected GetStatic(): XmlSchema {
517 | return this.constructor as XmlSchema;
518 | }
519 |
520 | protected GetPrefix(): string {
521 | return (this.Prefix) ? this.prefix + ":" : "";
522 | }
523 |
524 | protected OnGetXml(element: Element) {
525 | // Empty
526 | }
527 |
528 | protected CreateElement(document?: Document, localName?: string, namespaceUri: string | null = null, prefix: string | null = null) {
529 | if (!document) {
530 | document = this.CreateDocument()!;
531 | }
532 | localName = localName || this.localName;
533 | namespaceUri = namespaceUri || this.NamespaceURI;
534 | prefix = prefix || this.prefix;
535 |
536 | const tagName = (prefix ? `${prefix}:` : "") + localName;
537 | const xn = namespaceUri ? document!.createElementNS(namespaceUri, tagName) : document!.createElement(tagName);
538 | document!.importNode(xn, true);
539 |
540 | return xn;
541 | }
542 |
543 | protected CreateDocument() {
544 | return XmlObject.CreateDocument(
545 | this.localName,
546 | this.NamespaceURI,
547 | this.Prefix);
548 | }
549 | }
550 |
551 | import { XmlCollection } from "./xml_collection";
552 |
--------------------------------------------------------------------------------
/test/collection.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { Collection } from "../src";
3 |
4 | describe("Collection", () => {
5 |
6 | it("New empty Collection", () => {
7 | const col = new Collection();
8 | assert.equal(col.Count, 0);
9 | });
10 |
11 | it("New Collection from Array", () => {
12 | const col = new Collection([1, 2, 3, 4, 5]);
13 | assert.equal(col.Count, 5);
14 | });
15 |
16 | it("Get Item", () => {
17 | const col = new Collection([1, 2, 3, 4, 5]);
18 | assert.equal(col.Item(0), 1);
19 | assert.equal(col.Item(4), 5);
20 | assert.equal(col.Item(5), null);
21 | });
22 |
23 | it("Add Item", () => {
24 | const col = new Collection();
25 | assert.equal(col.Count, 0);
26 | col.Add(1);
27 | assert.equal(col.Count, 1);
28 | assert.equal(col.Item(0), 1);
29 | col.Add(2);
30 | assert.equal(col.Count, 2);
31 | assert.equal(col.Item(1), 2);
32 | });
33 |
34 | it("Pop", () => {
35 | const col = new Collection();
36 | col.Add(1);
37 | col.Add(2);
38 | col.Add(3);
39 | assert.equal(col.Count, 3);
40 | col.Pop();
41 | assert.equal(col.Count, 2);
42 | assert.equal(col.Item(0), 1);
43 | assert.equal(col.Item(1), 2);
44 | });
45 |
46 | it("RemoveAt", () => {
47 | const col = new Collection();
48 | col.Add(1);
49 | col.Add(2);
50 | col.Add(3);
51 | assert.equal(col.Count, 3);
52 | col.RemoveAt(0);
53 | assert.equal(col.Count, 2);
54 | assert.equal(col.Item(0), 2);
55 | assert.equal(col.Item(1), 3);
56 | });
57 |
58 | it("Clear", () => {
59 | const col = new Collection();
60 | col.Add(1);
61 | col.Add(2);
62 | col.Add(3);
63 | assert.equal(col.Count, 3);
64 | col.Clear();
65 | assert.equal(col.Count, 0);
66 | });
67 |
68 | it("IsEmpty", () => {
69 | const col = new Collection();
70 | assert.equal(col.IsEmpty(), true);
71 | col.Add(1);
72 | assert.equal(col.IsEmpty(), false);
73 | });
74 |
75 | it("Get Iterator", () => {
76 | const col = new Collection([1, 2, 3, 4, 5]);
77 | assert.equal(col.Count, 5);
78 | const array = col.GetIterator();
79 | assert.equal(Array.isArray(array), true);
80 | assert.equal(array.length, 5);
81 | });
82 |
83 | it("Each", () => {
84 | const col = new Collection([1, 2, 3, 4, 5]);
85 | assert.equal(col.Count, 5);
86 | col.ForEach((item, index) => assert.equal(item, index + 1));
87 | });
88 |
89 | it("Map", () => {
90 | const col = new Collection([1, 2, 3, 4, 5]);
91 | assert.equal(col.Count, 5);
92 | const array = col.Map((item, index) => item === index + 1).GetIterator();
93 | assert.equal(array.every((item) => item === true), true);
94 | });
95 |
96 | it("Filter", () => {
97 | const col = new Collection([1, 2, 1, 2, 1, 2, 1, 2]);
98 | assert.equal(col.Count, 8);
99 | const array = col.Filter((item, index) => item === 1).GetIterator();
100 | assert.equal(array.every((item) => item === 1), true);
101 | assert.equal(array.length, 4);
102 | });
103 |
104 | it("Sort", () => {
105 | const col = new Collection([1, 2, 3, 4, 5]);
106 | assert.equal(col.Count, 5);
107 | const array = col.Sort((a, b) => a < b ? 1 : a > b ? -1 : 0).GetIterator();
108 | const num = 5;
109 | array.forEach((item, index) => assert.equal(item === num - index, true));
110 | });
111 |
112 | it("Every", () => {
113 | const col = new Collection([1, 2, 3, 4, 5]);
114 | assert.equal(col.Count, 5);
115 | assert.equal(col.Every((item) => item < 6), true);
116 | assert.equal(col.Every((item) => item < 5), false);
117 | });
118 |
119 | it("Some", () => {
120 | const col = new Collection([1, 2, 3, 4, 5]);
121 | assert.equal(col.Count, 5);
122 | assert.equal(col.Some((item) => item < 6), true);
123 | assert.equal(col.Some((item) => item < 5), true);
124 | assert.equal(col.Some((item) => item > 5), false);
125 | });
126 |
127 | it("IsEmpty", () => {
128 | const col = new Collection();
129 | assert.equal(col.IsEmpty(), true);
130 | col.Add(1);
131 | assert.equal(col.IsEmpty(), false);
132 | });
133 |
134 | });
135 |
--------------------------------------------------------------------------------
/test/convert.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { Convert } from "../src";
3 |
4 | describe("Convert", () => {
5 |
6 | it("DateTime", () => {
7 | const date = new Date();
8 | const str = Convert.FromDateTime(date);
9 | assert.equal(str.length > 0, true);
10 | const newDate = Convert.ToDateTime(str);
11 | assert.equal(newDate.getTime(), date.getTime());
12 | });
13 |
14 | ["utf8", "binary", "hex", "base64", "base64url"].forEach((enc) => {
15 | [
16 | new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]),
17 | Convert.FromString("Привет", "utf8"),
18 | Convert.FromString("123456789", "binary"),
19 | Convert.FromString("010203040506070809", "hex"),
20 | Convert.FromString("Awa=", "base64"),
21 | Convert.FromString("Aw_=", "base64url"),
22 | ].forEach((buf, index) => {
23 | it(`Encoding ${enc} buf:${Convert.ToString(buf, enc)}`, () => {
24 | const str = Convert.ToString(buf, enc);
25 | assert.equal(typeof str, "string");
26 | const newBuf = Convert.FromString(str, enc);
27 | assert.equal(buf.every((c: number, i: number) => c === newBuf[i]), true);
28 | });
29 | });
30 | });
31 |
32 | });
33 |
--------------------------------------------------------------------------------
/test/converters.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { XmlAttribute, XmlElement, XmlObject } from "../src";
3 | import { XmlBase64Converter, XmlBooleanConverter, XmlNumberConverter } from "../src";
4 |
5 | describe("Convertors", () => {
6 |
7 | it("String", () => {
8 |
9 | @XmlElement({ localName: "test" })
10 | class XmlTest extends XmlObject {
11 |
12 | @XmlAttribute({})
13 | public Value: string;
14 |
15 | }
16 |
17 | const test = new XmlTest();
18 | test.Value = "";
19 | const xmlTest = test.toString();
20 | const xml = ``;
21 | assert.equal(xmlTest, xml);
22 |
23 | const test2 = XmlTest.LoadXml(xml);
24 | assert.equal(test2.Value, "");
25 | });
26 |
27 | it("Number", () => {
28 |
29 | @XmlElement({ localName: "test" })
30 | class XmlTest extends XmlObject {
31 |
32 | @XmlAttribute({ converter: XmlNumberConverter })
33 | public Value: number;
34 |
35 | }
36 |
37 | const test = new XmlTest();
38 | test.Value = 15;
39 | const xmlTest = test.toString();
40 | const xml = ``;
41 | assert.equal(xmlTest, xml);
42 |
43 | const test2 = XmlTest.LoadXml(xml);
44 | assert.equal(test2.Value, 15);
45 | });
46 |
47 | it("Base64", () => {
48 |
49 | @XmlElement({ localName: "test" })
50 | class XmlTest extends XmlObject {
51 |
52 | @XmlAttribute({ converter: XmlBase64Converter })
53 | public Value: Uint8Array;
54 |
55 | }
56 |
57 | const test = new XmlTest();
58 | test.Value = new Uint8Array([1, 0, 1]);
59 | const xmlTest = test.toString();
60 | const xml = ``;
61 | assert.equal(xmlTest, xml);
62 |
63 | const test2 = XmlTest.LoadXml(xml);
64 | assert.equal(test2.Value.length, 3);
65 | assert.equal(test2.Value[0], 1);
66 | assert.equal(test2.Value[1], 0);
67 | assert.equal(test2.Value[2], 1);
68 | });
69 |
70 | it("Boolean", () => {
71 |
72 | @XmlElement({ localName: "test" })
73 | class XmlTest extends XmlObject {
74 |
75 | @XmlAttribute({ converter: XmlBooleanConverter })
76 | public ValueTrue: boolean;
77 |
78 | @XmlAttribute({ converter: XmlBooleanConverter })
79 | public ValueFalse: boolean;
80 |
81 | }
82 |
83 | const test = new XmlTest();
84 | test.ValueTrue = true;
85 | test.ValueFalse = false;
86 | const xmlTest = test.toString();
87 | const xml = ``;
88 | assert.equal(xmlTest, xml);
89 |
90 | const test2 = XmlTest.LoadXml(xml);
91 | assert.equal(test2.ValueTrue, true);
92 | assert.equal(test2.ValueFalse, false);
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/test/decorators.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { Parse, XmlAttribute, XmlBase64Converter, XmlChildElement, XmlCollection, XmlContent, XmlElement, XmlNumberConverter, XmlObject } from "../src";
3 |
4 | describe("Decorators", () => {
5 |
6 | it("malformed", () => {
7 |
8 | @XmlElement({ localName: "test" })
9 | class XmlTest extends XmlObject {
10 | }
11 |
12 | const doc = Parse(``);
13 |
14 | const test = new XmlTest();
15 | assert.throws(() => {
16 | test.LoadXml(doc.documentElement);
17 | });
18 | });
19 |
20 | context("Element", () => {
21 |
22 | context("GetXml", () => {
23 |
24 | it("simple", () => {
25 | // Need some changes for changing element to null, otherwise empty answer
26 | @XmlElement({ localName: "test" })
27 | class Test extends XmlObject {
28 |
29 | @XmlAttribute({ localName: "id" })
30 | public Id: string;
31 |
32 | }
33 |
34 | const test = new Test();
35 |
36 | assert.equal(test.toString(), "");
37 |
38 | test.Id = "123";
39 | assert.equal(test.toString(), ``);
40 | });
41 |
42 | it("namespace", () => {
43 | @XmlElement({ localName: "test", namespaceURI: "http://some.com" })
44 | class Test extends XmlObject {
45 | @XmlAttribute({ localName: "id", defaultValue: "1" })
46 | public Id: string = "test";
47 | }
48 |
49 | const test = new Test();
50 |
51 | assert.equal(test.toString(), ``);
52 | test.Id = "1";
53 | assert.equal(test.toString(), ``);
54 | });
55 |
56 | it("prefix with namespace", () => {
57 | @XmlElement({ localName: "test", prefix: "sm", namespaceURI: "http://some.com" })
58 | class Test extends XmlObject {
59 | @XmlAttribute({ localName: "id" })
60 | public Id: string = "test";
61 | }
62 |
63 | const test = new Test();
64 |
65 | assert.equal(test.toString(), ``);
66 | });
67 |
68 | it("prefix without namespace", () => {
69 | @XmlElement({ localName: "test", prefix: "sm" })
70 | class Test extends XmlObject {
71 | @XmlAttribute({ localName: "id", defaultValue: "" })
72 | public Id: string = "test";
73 | }
74 |
75 | const test = new Test();
76 |
77 | assert.equal(test.toString(), ``);
78 | });
79 |
80 | context("simple child", () => {
81 | it("default value", () => {
82 | @XmlElement({ localName: "test" })
83 | class Test extends XmlObject {
84 | @XmlChildElement({ defaultValue: "" })
85 | public Child: string;
86 | }
87 |
88 | const test = new Test();
89 |
90 | test.Child = "Hello";
91 | assert.equal(test.toString(), `Hello`);
92 | });
93 |
94 | it("default value and required", () => {
95 | @XmlElement({ localName: "test" })
96 | class Test extends XmlObject {
97 | @XmlChildElement({ required: true })
98 | public Child: string = "1";
99 | }
100 |
101 | const test = new Test();
102 |
103 | assert.equal(test.toString(), `1`);
104 | });
105 |
106 | it("changed name", () => {
107 | @XmlElement({ localName: "test" })
108 | class Test extends XmlObject {
109 | @XmlChildElement({ localName: "ch", defaultValue: "1" })
110 | public Child: string;
111 | }
112 |
113 | const test = new Test();
114 |
115 | test.Child = "Hello";
116 | assert.equal(test.toString(), `Hello`);
117 | });
118 |
119 | it("namespace", () => {
120 | @XmlElement({ localName: "test" })
121 | class Test extends XmlObject {
122 | @XmlChildElement({ localName: "ch", defaultValue: "1", namespaceURI: "http://some.com" })
123 | public Child: string;
124 | }
125 |
126 | const test = new Test();
127 |
128 | test.Child = "Hello";
129 | assert.equal(test.toString(), `Hello`);
130 | });
131 |
132 | it("prefix", () => {
133 | @XmlElement({ localName: "test" })
134 | class Test extends XmlObject {
135 | @XmlChildElement({ localName: "ch", defaultValue: "1", namespaceURI: "http://some.com", prefix: "px" })
136 | public Child: string;
137 | }
138 |
139 | const test = new Test();
140 |
141 | test.Child = "Hello";
142 | assert.equal(test.toString(), `Hello`);
143 | });
144 | });
145 |
146 | context("Child", () => {
147 |
148 | @XmlElement({ localName: "base" })
149 | class XmlBase extends XmlObject {
150 | @XmlAttribute({ localName: "id", defaultValue: "" })
151 | public Id: string;
152 | }
153 |
154 | @XmlElement({ localName: "child1" })
155 | class Child1 extends XmlBase {
156 | @XmlChildElement({ localName: "text", defaultValue: 5, converter: XmlNumberConverter })
157 | public Value: number;
158 | }
159 | @XmlElement({ localName: "child2", namespaceURI: "http://number.com" })
160 | class Child2 extends XmlBase {
161 | @XmlChildElement({ localName: "text", defaultValue: new Uint8Array([1, 0, 1]), converter: XmlBase64Converter, required: true })
162 | public Value: Uint8Array;
163 | }
164 |
165 | @XmlElement({ localName: "root" })
166 | class Root extends XmlBase {
167 | @XmlChildElement({ localName: "name", required: true })
168 | public Name: string;
169 |
170 | @XmlChildElement({ parser: Child1 })
171 | public ChildOptional: Child1;
172 | @XmlChildElement({ parser: Child2, required: true })
173 | public ChildRequired: Child2;
174 | }
175 |
176 | @XmlElement({ localName: "Item" })
177 | class Item extends XmlObject {
178 | @XmlChildElement({ localName: "Id"})
179 | public id: number;
180 | @XmlChildElement({ localName: "Name"})
181 | public name: string;
182 | }
183 | it("default", () => {
184 | const root = new Root();
185 | root.Name = "MyName";
186 |
187 | root.ChildRequired = new Child2();
188 |
189 | assert.equal(root.toString(), `MyNameAQAB`);
190 |
191 | root.ChildOptional.Id = "10";
192 | root.ChildOptional.Value = 12;
193 | root.ChildRequired.Value = new Uint8Array([1, 1, 1]);
194 |
195 | assert.equal(root.toString(), `MyName12AQEB`);
196 | });
197 |
198 | it("handle collection as simple array", () => {
199 | @XmlElement({ localName: "test" })
200 | class Test extends XmlObject {
201 | @XmlChildElement({ defaultValue: [] })
202 | public Items: Array- ;
203 | }
204 |
205 | const test = new Test();
206 |
207 | test.Items = [
208 | new Item({id: 1, name: 'Item 1'}),
209 | new Item({id: 2, name: 'Item 2'}),
210 | new Item({id: 3, name: 'Item 3'})
211 | ];
212 | assert.equal(test.toString(), `
- 1Item 1
- 2Item 2
- 3Item 3
`);
213 | });
214 | });
215 | });
216 |
217 | context("LoadXml", () => {
218 |
219 | context("simple", () => {
220 |
221 | it("value", () => {
222 |
223 | @XmlElement({ localName: "test" })
224 | class XmlTest extends XmlObject {
225 |
226 | @XmlChildElement({ localName: "value", defaultValue: "" })
227 | public Value: string;
228 |
229 | }
230 |
231 | const doc = Parse(`123`);
232 |
233 | const test = new XmlTest();
234 | test.LoadXml(doc.documentElement);
235 |
236 | assert.equal(test.Value, "123");
237 |
238 | });
239 |
240 | it("required with error", () => {
241 |
242 | @XmlElement({ localName: "test" })
243 | class XmlTest extends XmlObject {
244 |
245 | @XmlChildElement({ localName: "value", defaultValue: "", required: true })
246 | public Value: string;
247 |
248 | }
249 |
250 | const doc = Parse(``);
251 |
252 | const test = new XmlTest();
253 |
254 | assert.throws(() => {
255 | test.LoadXml(doc.documentElement);
256 | });
257 |
258 | });
259 |
260 | it("required", () => {
261 |
262 | @XmlElement({ localName: "test" })
263 | class XmlTest extends XmlObject {
264 |
265 | @XmlChildElement({ localName: "value", defaultValue: "", required: true })
266 | public Value: string;
267 |
268 | }
269 |
270 | const doc = Parse(`123`);
271 |
272 | const test = new XmlTest();
273 | test.LoadXml(doc.documentElement);
274 |
275 | assert.equal(test.Value, "123");
276 |
277 | });
278 |
279 | it("converter", () => {
280 |
281 | @XmlElement({ localName: "test" })
282 | class XmlTest extends XmlObject {
283 |
284 | @XmlChildElement({ localName: "value", defaultValue: null, converter: XmlBase64Converter })
285 | public Value: Uint8Array | null;
286 |
287 | }
288 |
289 | const doc = Parse(`AQAB`);
290 |
291 | const test = new XmlTest();
292 | test.LoadXml(doc.documentElement);
293 |
294 | assert.equal(test.Value instanceof Uint8Array, true);
295 | assert.equal(test.Value!.length, 3);
296 |
297 | });
298 |
299 | it("namespace", () => {
300 |
301 | @XmlElement({ localName: "test" })
302 | class XmlTest extends XmlObject {
303 |
304 | @XmlChildElement({ localName: "value", defaultValue: "", namespaceURI: "http://some.com" })
305 | public Value: string;
306 |
307 | }
308 |
309 | const doc = Parse(`Text`);
310 |
311 | const test = new XmlTest();
312 | test.LoadXml(doc.documentElement);
313 |
314 | assert.equal(test.Value, "Text");
315 | });
316 |
317 | });
318 |
319 | context("child", () => {
320 |
321 | it("simple", () => {
322 | @XmlElement({ localName: "value" })
323 | class XmlChild extends XmlObject {
324 |
325 | @XmlAttribute({ localName: "id", defaultValue: "" })
326 | public Id: string;
327 |
328 | }
329 |
330 | @XmlElement({ localName: "test" })
331 | class XmlTest extends XmlObject {
332 |
333 | @XmlChildElement({ required: true, parser: XmlChild })
334 | public Value: XmlChild;
335 |
336 | }
337 |
338 | const test = new XmlTest();
339 | test.LoadXml(``);
340 |
341 | assert.equal(test.Value.Id, "123");
342 | });
343 |
344 | it("required", () => {
345 | @XmlElement({ localName: "value" })
346 | class XmlChild extends XmlObject {
347 |
348 | @XmlAttribute({ localName: "id", defaultValue: "" })
349 | public Id: string;
350 |
351 | }
352 |
353 | @XmlElement({ localName: "test" })
354 | class XmlTest extends XmlObject {
355 |
356 | @XmlChildElement({ required: true, parser: XmlChild })
357 | public Value: XmlChild;
358 |
359 | }
360 |
361 | const doc = Parse(``);
362 |
363 | const test = new XmlTest();
364 | assert.throws(() => {
365 | test.LoadXml(doc.documentElement);
366 | });
367 |
368 | });
369 |
370 | it("namespace", () => {
371 | @XmlElement({ localName: "value", namespaceURI: "http://some.com" })
372 | class XmlChild extends XmlObject {
373 |
374 | @XmlAttribute({ localName: "id", defaultValue: "" })
375 | public Id: string;
376 |
377 | }
378 |
379 | @XmlElement({ localName: "test" })
380 | class XmlTest extends XmlObject {
381 |
382 | @XmlChildElement({ required: true, parser: XmlChild })
383 | public Value: XmlChild;
384 |
385 | }
386 |
387 | const doc = Parse(``);
388 |
389 | const test = new XmlTest();
390 | test.LoadXml(doc.documentElement);
391 |
392 | assert.equal(test.Value.Id, "123");
393 | assert.equal(test.Value.Prefix, "p");
394 | assert.equal(test.Value.NamespaceURI, "http://some.com");
395 |
396 | });
397 |
398 | });
399 |
400 | });
401 |
402 | });
403 |
404 | context("Attribute", () => {
405 |
406 | context("GetXml", () => {
407 |
408 | @XmlElement({ localName: "test" })
409 | class Test extends XmlObject {
410 |
411 | @XmlAttribute()
412 | public Id?: string;
413 |
414 | @XmlAttribute({ localName: "num", defaultValue: 0, converter: XmlNumberConverter })
415 | public ConvertNumber?: number;
416 |
417 | @XmlAttribute({ localName: "b64", converter: XmlBase64Converter })
418 | public ConvertB64?: Uint8Array;
419 |
420 | @XmlAttribute({ defaultValue: "none" })
421 | public Attr1?: string;
422 |
423 | @XmlAttribute({ required: true, localName: "required" })
424 | public Required: string;
425 |
426 | protected name = "test";
427 | }
428 |
429 | const test = new Test();
430 |
431 | it("with required empty attribute", () => {
432 |
433 | assert.equal(test.toString(), "");
434 |
435 | });
436 |
437 | it("with filled empty attribute and lower case name", () => {
438 | test.Required = "some";
439 | assert.equal(test.toString(), ``);
440 | });
441 |
442 | it("with different default value", () => {
443 | assert.equal(test.Attr1, "none", "Doesn't have default value for decoration setting");
444 | test.Attr1 = "wow";
445 | assert.equal(test.Attr1, "wow");
446 | assert.equal(test.toString(), ``);
447 | });
448 |
449 | it("with default value", () => {
450 | test.Attr1 = "none";
451 | assert.equal(test.Attr1, "none");
452 | assert.equal(test.toString(), ``);
453 | });
454 |
455 | it("with number converter value", () => {
456 | test.ConvertNumber = 1;
457 | assert.equal(test.toString(), ``);
458 | });
459 |
460 | it("with base64 converter value", () => {
461 | test.ConvertNumber = 0;
462 | test.ConvertB64 = new Uint8Array([1, 0, 1]);
463 | assert.equal(test.toString(), ``);
464 | });
465 |
466 | it("witd Id", () => {
467 | test.ConvertB64 = undefined; // remove value
468 | test.Id = "123";
469 | assert.equal(test.toString(), ``);
470 | });
471 |
472 | });
473 |
474 | context("LoadXml", () => {
475 |
476 | it("simple", () => {
477 |
478 | @XmlElement({ localName: "test" })
479 | class XmlTest extends XmlObject {
480 |
481 | @XmlAttribute({ localName: "id", defaultValue: "" })
482 | public Id: string;
483 |
484 | }
485 |
486 | const doc = Parse(``);
487 |
488 | const test = new XmlTest();
489 | test.LoadXml(doc.documentElement);
490 |
491 | assert.equal(test.Id, "123");
492 |
493 | });
494 |
495 | it("required with error", () => {
496 |
497 | @XmlElement({ localName: "test" })
498 | class XmlTest extends XmlObject {
499 |
500 | @XmlAttribute({ localName: "id", required: true })
501 | public Id: string;
502 |
503 | }
504 |
505 | const doc = Parse(``);
506 |
507 | const test = new XmlTest();
508 |
509 | assert.throws(() => {
510 | test.LoadXml(doc.documentElement);
511 | });
512 |
513 | });
514 |
515 | it("required", () => {
516 |
517 | @XmlElement({ localName: "test" })
518 | class XmlTest extends XmlObject {
519 |
520 | @XmlAttribute({ localName: "id", required: true })
521 | public Id: string;
522 |
523 | }
524 |
525 | const doc = Parse(``);
526 |
527 | const test = new XmlTest();
528 | test.LoadXml(doc.documentElement);
529 |
530 | assert.equal(test.Id, "123");
531 | });
532 |
533 | it("namespace", () => {
534 |
535 | @XmlElement({ localName: "test" })
536 | class XmlTest extends XmlObject {
537 |
538 | @XmlAttribute({ localName: "id", defaultValue: "", required: true, namespaceURI: "http://some.com" })
539 | public Id: string;
540 |
541 | }
542 |
543 | {
544 | // correct
545 | const doc = Parse(``);
546 |
547 | const test = new XmlTest();
548 | test.LoadXml(doc.documentElement);
549 |
550 | assert.equal(test.Id, "123");
551 | }
552 |
553 | {
554 | // error
555 | const doc = Parse(``);
556 |
557 | const test = new XmlTest();
558 | assert.throws(() => {
559 | test.LoadXml(doc.documentElement);
560 | });
561 | }
562 | });
563 |
564 | it("converter", () => {
565 |
566 | @XmlElement({ localName: "test" })
567 | class XmlTest extends XmlObject {
568 |
569 | @XmlAttribute({ localName: "value", defaultValue: null, converter: XmlBase64Converter })
570 | public Value: Uint8Array | null;
571 |
572 | }
573 |
574 | const doc = Parse(``);
575 |
576 | const test = new XmlTest();
577 | test.LoadXml(doc.documentElement);
578 |
579 | assert.equal(test.Value instanceof Uint8Array, true);
580 | assert.equal(test.Value!.length, 3);
581 | });
582 |
583 | });
584 |
585 | });
586 |
587 | context("Collection", () => {
588 |
589 | context("GetXml", () => {
590 |
591 | it("simple", () => {
592 |
593 | @XmlElement({ localName: "transform" })
594 | class XmlTransform extends XmlObject {
595 | @XmlChildElement({ defaultValue: "" })
596 | public Value: string;
597 | }
598 |
599 | @XmlElement({ localName: "transforms", parser: XmlTransform })
600 | class XmlTransforms extends XmlCollection {
601 |
602 | }
603 |
604 | @XmlElement({ localName: "test" })
605 | class XmlTest extends XmlObject {
606 | @XmlChildElement({ minOccurs: 1, parser: XmlTransforms })
607 | public Transforms: XmlTransforms;
608 | }
609 |
610 | const test = new XmlTest();
611 |
612 | test.Transforms = new XmlTransforms();
613 | const t = new XmlTransform();
614 | t.Value = "Hello";
615 | test.Transforms.Add(t);
616 |
617 | assert.equal(test.toString(), `Hello`);
618 |
619 | });
620 |
621 | it("no root", () => {
622 |
623 | @XmlElement({ localName: "transform" })
624 | class XmlTransform extends XmlObject {
625 | @XmlChildElement({ defaultValue: "" })
626 | public Value: string;
627 | }
628 |
629 | @XmlElement({ localName: "transforms", parser: XmlTransform })
630 | class XmlTransforms extends XmlCollection {
631 |
632 | }
633 |
634 | @XmlElement({ localName: "test" })
635 | class XmlTest extends XmlObject {
636 | @XmlChildElement({ minOccurs: 1, parser: XmlTransforms, noRoot: true })
637 | public Transforms: XmlTransforms;
638 | }
639 |
640 | const test = new XmlTest();
641 |
642 | test.Transforms = new XmlTransforms();
643 | const t = new XmlTransform();
644 | t.Value = "Hello";
645 | test.Transforms.Add(t);
646 |
647 | assert.equal(test.toString(), `Hello`);
648 |
649 | });
650 |
651 | it("no root occurs", () => {
652 |
653 | @XmlElement({ localName: "transform" })
654 | class XmlTransform extends XmlObject {
655 | @XmlChildElement({ defaultValue: "" })
656 | public Value: string;
657 | constructor(value?: string) {
658 | super();
659 | if (value) {
660 | this.Value = value;
661 | }
662 | }
663 | }
664 |
665 | @XmlElement({ localName: "transforms", parser: XmlTransform })
666 | class XmlTransforms extends XmlCollection {
667 | }
668 |
669 | @XmlElement({ localName: "test" })
670 | class XmlTest extends XmlObject {
671 | @XmlChildElement({ minOccurs: 1, maxOccurs: 4, parser: XmlTransforms, noRoot: true })
672 | public Transforms: XmlTransforms;
673 | }
674 |
675 | const test = new XmlTest();
676 |
677 | test.Transforms = new XmlTransforms();
678 |
679 | assert.throws(() => {
680 | test.toString();
681 | });
682 |
683 | test.Transforms.Add(new XmlTransform("1"));
684 | test.Transforms.Add(new XmlTransform("2"));
685 | test.Transforms.Add(new XmlTransform("3"));
686 | test.Transforms.Add(new XmlTransform("4"));
687 | assert.equal(test.toString(), `1234`);
688 |
689 | test.Transforms.Add(new XmlTransform("4"));
690 | assert.throws(() => {
691 | test.toString();
692 | });
693 | });
694 |
695 | it("occurs", () => {
696 |
697 | @XmlElement({ localName: "transform" })
698 | class XmlTransform extends XmlObject {
699 | @XmlChildElement({ defaultValue: "" })
700 | public Value: string;
701 | constructor(value?: string) {
702 | super();
703 | if (value) {
704 | this.Value = value;
705 | }
706 | }
707 | }
708 |
709 | @XmlElement({ localName: "transforms", parser: XmlTransform })
710 | class XmlTransforms extends XmlCollection {
711 | }
712 |
713 | @XmlElement({ localName: "test" })
714 | class XmlTest extends XmlObject {
715 | @XmlChildElement({ minOccurs: 1, maxOccurs: 4, parser: XmlTransforms })
716 | public Transforms: XmlTransforms;
717 | }
718 |
719 | const test = new XmlTest();
720 |
721 | test.Transforms = new XmlTransforms();
722 |
723 | assert.throws(() => {
724 | test.toString();
725 | });
726 |
727 | test.Transforms.Add(new XmlTransform("1"));
728 | test.Transforms.Add(new XmlTransform("2"));
729 | test.Transforms.Add(new XmlTransform("3"));
730 | test.Transforms.Add(new XmlTransform("4"));
731 | assert.equal(test.toString(), `1234`);
732 |
733 | test.Transforms.Add(new XmlTransform("4"));
734 | assert.throws(() => {
735 | test.toString();
736 | });
737 | });
738 |
739 | });
740 |
741 | context("LoadXml", () => {
742 |
743 | it("simple", () => {
744 |
745 | @XmlElement({ localName: "transform" })
746 | class XmlTransform extends XmlObject {
747 | @XmlChildElement({ defaultValue: "" })
748 | public Value: string;
749 | }
750 |
751 | @XmlElement({ localName: "transforms", parser: XmlTransform })
752 | class XmlTransforms extends XmlCollection {
753 |
754 | }
755 |
756 | @XmlElement({ localName: "test" })
757 | class XmlTest extends XmlObject {
758 | @XmlChildElement({ minOccurs: 1, parser: XmlTransforms })
759 | public Transforms: XmlTransforms;
760 | }
761 |
762 | const doc = Parse("Hello");
763 | const test = new XmlTest();
764 | test.LoadXml(doc.documentElement);
765 |
766 | assert.equal(test.Transforms.Count, 1);
767 | assert.equal(test.Transforms.Item(0)!.Value, "Hello");
768 |
769 | });
770 |
771 | it("no root", () => {
772 |
773 | @XmlElement({ localName: "transform" })
774 | class XmlTransform extends XmlObject {
775 | @XmlChildElement({ defaultValue: "" })
776 | public Value: string;
777 | }
778 |
779 | @XmlElement({ localName: "transforms", parser: XmlTransform })
780 | class XmlTransforms extends XmlCollection {
781 |
782 | }
783 |
784 | @XmlElement({ localName: "test" })
785 | class XmlTest extends XmlObject {
786 | @XmlChildElement({ minOccurs: 1, parser: XmlTransforms, noRoot: true })
787 | public Transforms: XmlTransforms;
788 | }
789 |
790 | const doc = Parse("Hello");
791 | const test = new XmlTest();
792 | test.LoadXml(doc.documentElement);
793 |
794 | assert.equal(test.Transforms.Count, 1);
795 | assert.equal(test.Transforms.Item(0)!.Value, "Hello");
796 |
797 | });
798 |
799 | it("wrong min occurs", () => {
800 |
801 | @XmlElement({ localName: "transform" })
802 | class XmlTransform extends XmlObject {
803 | @XmlChildElement({ defaultValue: "" })
804 | public Value: string;
805 | }
806 |
807 | @XmlElement({ localName: "transforms", parser: XmlTransform })
808 | class XmlTransforms extends XmlCollection {
809 |
810 | }
811 |
812 | @XmlElement({ localName: "test" })
813 | class XmlTest extends XmlObject {
814 | @XmlChildElement({ minOccurs: 2, parser: XmlTransforms, noRoot: true })
815 | public Transforms: XmlTransforms;
816 | }
817 |
818 | const doc = Parse("Hello");
819 | const test = new XmlTest();
820 |
821 | assert.throws(() => {
822 | test.LoadXml(doc.documentElement);
823 | });
824 |
825 | });
826 | it("wrong max occurs", () => {
827 |
828 | @XmlElement({ localName: "transform" })
829 | class XmlTransform extends XmlObject {
830 | @XmlChildElement({ defaultValue: "" })
831 | public Value: string;
832 | }
833 |
834 | @XmlElement({ localName: "transforms", parser: XmlTransform })
835 | class XmlTransforms extends XmlCollection {
836 |
837 | }
838 |
839 | @XmlElement({ localName: "test" })
840 | class XmlTest extends XmlObject {
841 | @XmlChildElement({ maxOccurs: 1, parser: XmlTransforms, noRoot: true })
842 | public Transforms: XmlTransforms;
843 | }
844 |
845 | const doc = Parse("HelloHello");
846 | const test = new XmlTest();
847 |
848 | assert.throws(() => {
849 | test.LoadXml(doc.documentElement);
850 | });
851 |
852 | });
853 |
854 | });
855 |
856 | });
857 |
858 | it("extends", () => {
859 |
860 | @XmlElement({
861 | localName: "first",
862 | namespaceURI: "http://some.com",
863 | prefix: "p",
864 | })
865 | class XmlFirst extends XmlObject {
866 |
867 | @XmlAttribute()
868 | public Id: string;
869 |
870 | }
871 |
872 | @XmlElement({
873 | localName: "second",
874 | })
875 | class XmlSecond extends XmlFirst {
876 | }
877 |
878 | const first = new XmlFirst();
879 | first.Id = "1";
880 | assert.equal(first.toString(), ``);
881 |
882 | const second = new XmlSecond();
883 | second.Id = "2";
884 | assert.equal(second.toString(), ``);
885 | assert.equal((XmlFirst as any).prefix, (XmlSecond as any).prefix);
886 | assert.equal((XmlFirst as any).namespaceURI, (XmlSecond as any).namespaceURI);
887 |
888 | });
889 |
890 | context("XmlContent", () => {
891 |
892 | it("Content", () => {
893 |
894 | @XmlElement({
895 | localName: "test",
896 | namespaceURI: "http://some.com",
897 | prefix: "p",
898 | })
899 | class XmlTest extends XmlObject {
900 |
901 | @XmlContent()
902 | public Value: string;
903 |
904 | }
905 |
906 | const test = new XmlTest();
907 |
908 | assert.equal(test.Value, undefined);
909 |
910 | test.Value = "Test";
911 |
912 | const xml = test.toString();
913 | assert.equal(xml, `Test`);
914 | const test2 = XmlTest.LoadXml(xml);
915 |
916 | assert.equal(test2.Value, test.Value);
917 | });
918 |
919 | it("Converter", () => {
920 |
921 | @XmlElement({
922 | localName: "test",
923 | namespaceURI: "http://some.com",
924 | prefix: "p",
925 | })
926 | class XmlTest extends XmlObject {
927 |
928 | @XmlContent({
929 | converter: XmlNumberConverter,
930 | })
931 | public Value: number;
932 |
933 | }
934 |
935 | const test = new XmlTest();
936 |
937 | assert.equal(test.Value, undefined);
938 |
939 | test.Value = 123;
940 |
941 | const xml = test.toString();
942 | assert.equal(xml, `123`);
943 |
944 | const test2 = XmlTest.LoadXml(xml);
945 |
946 | assert.equal(test2.Value, test.Value);
947 | });
948 |
949 | it("Required", () => {
950 |
951 | @XmlElement({
952 | localName: "test",
953 | namespaceURI: "http://some.com",
954 | prefix: "p",
955 | })
956 | class XmlTest extends XmlObject {
957 |
958 | @XmlAttribute()
959 | public Id: string;
960 |
961 | @XmlContent({
962 | required: true,
963 | })
964 | public Value: string;
965 |
966 | }
967 |
968 | const test = new XmlTest();
969 |
970 | assert.equal(test.Value, undefined);
971 |
972 | test.Id = "1";
973 |
974 | assert.throws(() => test.GetXml());
975 |
976 | test.Value = "test";
977 |
978 | const xml = test.toString();
979 | assert.equal(xml, `test`);
980 |
981 | assert.throws(() => XmlTest.LoadXml(``));
982 |
983 | const test2 = XmlTest.LoadXml(xml);
984 |
985 | assert.equal(test2.Value, test.Value);
986 | });
987 |
988 | it("Default value", () => {
989 | @XmlElement({
990 | localName: "test",
991 | namespaceURI: "http://some.com",
992 | prefix: "p",
993 | })
994 | class XmlTest extends XmlObject {
995 |
996 | @XmlAttribute()
997 | public Id: string;
998 |
999 | @XmlContent({
1000 | required: true,
1001 | defaultValue: "test",
1002 | })
1003 | public Value: string;
1004 |
1005 | }
1006 |
1007 | const test = new XmlTest();
1008 |
1009 | assert.equal(test.Value, "test");
1010 |
1011 | test.Id = "1";
1012 |
1013 | const xml = test.toString();
1014 | assert.equal(xml, `test`);
1015 |
1016 | assert.throws(() => XmlTest.LoadXml(``));
1017 |
1018 | const test2 = XmlTest.LoadXml(xml);
1019 |
1020 | assert.equal(test2.Value, test.Value);
1021 | });
1022 |
1023 | });
1024 |
1025 | });
1026 |
--------------------------------------------------------------------------------
/test/error.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { XE, XmlError } from "../src";
3 |
4 | describe("Error", () => {
5 |
6 | it("Throw error", () => {
7 | assert.throws(() => {
8 | throw new XmlError(XE.NONE);
9 | });
10 | });
11 |
12 | it("Error params", () => {
13 | const error = new XmlError(XE.NULL_REFERENCE);
14 | assert.equal(error.code, 1);
15 | assert.equal(error.name, "XmlError");
16 | assert.equal(error.message, "XMLJS0001: Null reference");
17 | });
18 |
19 | it("Error template", () => {
20 | const error = new XmlError(XE.NULL_PARAM, "Object", "name");
21 | assert.equal(error.code, 2);
22 | // assert.equal(error.name, "XmlError");
23 | assert.equal(error.message, "XMLJS0002: 'Object' has empty 'name' object");
24 | });
25 |
26 | });
27 |
--------------------------------------------------------------------------------
/test/get_load.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { XmlAttribute, XmlChildElement, XmlElement } from "../src";
3 | import { XmlCollection, XmlObject } from "../src";
4 | import { XmlNumberConverter } from "../src";
5 |
6 | context("GetXml/LoadXml/HasChanged", () => {
7 |
8 | it("Simple", () => {
9 | @XmlElement({
10 | localName: "test",
11 | namespaceURI: "http://some.com",
12 | })
13 | class Test extends XmlObject {
14 |
15 | @XmlAttribute({ localName: "id", defaultValue: "1" })
16 | public Id: string;
17 | @XmlAttribute({ localName: "class", defaultValue: "2", required: true })
18 | public Class: string;
19 |
20 | @XmlChildElement({ localName: "algorithm", namespaceURI: "http://some.com", defaultValue: "3" })
21 | public Algorithm: string;
22 | @XmlChildElement({ localName: "method", namespaceURI: "http://some.com", defaultValue: "4", required: true })
23 | public Method: string;
24 | }
25 |
26 | const t = new Test();
27 |
28 | assert.equal(t.toString(), "", "initialized class should be empty");
29 |
30 | t.Id = "123";
31 |
32 | const xml = `4`;
33 | assert.equal(t.toString(), xml);
34 |
35 | const p = new Test();
36 | p.LoadXml(xml);
37 |
38 | assert.equal(p.Id, "123");
39 | assert.equal(p.Class, "2");
40 | assert.equal(p.Algorithm, "3");
41 | assert.equal(p.Method, "4");
42 | assert.equal(p.HasChanged(), false);
43 | });
44 |
45 | it("With child element", () => {
46 |
47 | @XmlElement({
48 | localName: "child",
49 | namespaceURI: "http://some.com",
50 | })
51 | class Child extends XmlObject {
52 | @XmlAttribute({ localName: "id", defaultValue: "" })
53 | public Id: string;
54 | }
55 |
56 | @XmlElement({
57 | localName: "test",
58 | namespaceURI: "http://some.com",
59 | })
60 | class Test extends XmlObject {
61 |
62 | @XmlChildElement({ parser: Child })
63 | public Child: Child;
64 | }
65 |
66 | const t = new Test();
67 |
68 | assert.equal(t.toString(), "", "initialized class should be empty");
69 | assert.equal(t.HasChanged(), false);
70 |
71 | t.Child.Id = "1";
72 |
73 | const xml = ``;
74 |
75 | assert.equal(t.HasChanged(), true);
76 | assert.equal(t.toString(), xml);
77 | assert.equal(t.HasChanged(), false);
78 |
79 | const p = Test.LoadXml(xml);
80 |
81 | assert.equal(!!p.Child, true);
82 | assert.equal(p.Child.Id, "1");
83 | assert.equal(p.Child.NamespaceURI, "http://some.com");
84 | assert.equal(p.HasChanged(), false);
85 | });
86 |
87 | it("With child XmlCollection", () => {
88 |
89 | @XmlElement({
90 | localName: "child",
91 | namespaceURI: "http://some.com",
92 | })
93 | class Child extends XmlObject {
94 | @XmlAttribute({ localName: "id", defaultValue: "" })
95 | public Id: string;
96 | }
97 |
98 | @XmlElement({
99 | localName: "childs",
100 | namespaceURI: "http://some.com",
101 | parser: Child,
102 | })
103 | class Childs extends XmlCollection {
104 | }
105 |
106 | @XmlElement({
107 | localName: "test",
108 | namespaceURI: "http://some.com",
109 | })
110 | class Test extends XmlObject {
111 |
112 | @XmlChildElement({ parser: Childs })
113 | public Childs: Childs;
114 | }
115 |
116 | const t = new Test();
117 |
118 | assert.equal(t.toString(), "", "initialized class should be empty");
119 |
120 | t.Childs.Add(new Child());
121 |
122 | const xml = ``;
123 |
124 | assert.equal(t.toString(), xml);
125 |
126 | const p = Test.LoadXml(xml);
127 |
128 | assert.equal(p.Childs.Count, 0);
129 | assert.equal(p.HasChanged(), false);
130 | });
131 |
132 | it("With child requierd XmlCollection", () => {
133 |
134 | @XmlElement({
135 | localName: "child",
136 | namespaceURI: "http://some.com",
137 | })
138 | class Child extends XmlObject {
139 | @XmlAttribute({ localName: "id", defaultValue: "" })
140 | public Id: string;
141 | }
142 |
143 | @XmlElement({
144 | localName: "childs",
145 | namespaceURI: "http://some.com",
146 | parser: Child,
147 | })
148 | class Childs extends XmlCollection {
149 | }
150 |
151 | @XmlElement({
152 | localName: "test",
153 | namespaceURI: "http://some.com",
154 | })
155 | class Test extends XmlObject {
156 |
157 | @XmlChildElement({ parser: Childs })
158 | public Childs: Childs;
159 | @XmlChildElement({ localName: "required", parser: Childs, minOccurs: 1 })
160 | public Childs2: Childs;
161 | }
162 |
163 | const t = new Test();
164 |
165 | assert.equal(t.toString(), "");
166 | // assert.throws(() => t.toString());
167 |
168 | t.Childs.Add(new Child());
169 |
170 | assert.throws(() => t.toString());
171 | t.Childs2.Add(new Child());
172 | assert.throws(() => t.toString());
173 | t.Childs2.Item(0) !.Id = "test";
174 |
175 | const xml = ``;
176 |
177 | assert.equal(t.toString(), xml);
178 |
179 | const p = Test.LoadXml(xml);
180 | assert.equal(p.Childs.Count, 0);
181 | assert.equal(p.Childs2.LocalName, "required");
182 | assert.equal(p.Childs2.Count, 1);
183 | assert.equal(p.Childs2.Item(0) !.Id, "test");
184 | assert.equal(p.HasChanged(), false);
185 | });
186 |
187 | it("praser for attributes", () => {
188 |
189 | @XmlElement({
190 | localName: "test",
191 | namespaceURI: "https://some.com",
192 | })
193 | class Test extends XmlObject {
194 |
195 | @XmlAttribute({
196 | localName: "value",
197 | })
198 | public Value = "test";
199 |
200 | @XmlAttribute({
201 | localName: "version",
202 | converter: XmlNumberConverter,
203 | })
204 | public Version: number;
205 |
206 | }
207 |
208 | const t = new Test();
209 |
210 | let xml = ``;
211 | assert.equal(t.toString(), xml);
212 | Test.LoadXml(xml);
213 |
214 | t.Version = 1;
215 |
216 | xml = ``;
217 | assert.equal(t.toString(), xml);
218 | Test.LoadXml(xml);
219 | });
220 |
221 | it("praser for child element", () => {
222 |
223 | @XmlElement({
224 | localName: "test",
225 | namespaceURI: "https://some.com",
226 | })
227 | class Test extends XmlObject {
228 |
229 | @XmlChildElement({
230 | localName: "value",
231 | namespaceURI: "https://some.com",
232 | })
233 | public Value = "test";
234 |
235 | @XmlChildElement({
236 | localName: "version",
237 | namespaceURI: "https://some.com",
238 | converter: XmlNumberConverter,
239 | })
240 | public Version: number;
241 |
242 | }
243 |
244 | const t = new Test();
245 |
246 | let xml = `test`;
247 | assert.equal(t.toString(), xml);
248 | Test.LoadXml(xml);
249 |
250 | t.Version = 1;
251 |
252 | xml = `test1`;
253 | assert.equal(t.toString(), xml);
254 | Test.LoadXml(xml);
255 | });
256 |
257 | });
258 |
--------------------------------------------------------------------------------
/test/namespace_manager.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { NamespaceManager } from "../src";
3 |
4 | context("NamespaceManager", () => {
5 |
6 | const nm = new NamespaceManager();
7 |
8 | before(() => {
9 | nm.Add({
10 | prefix: "n1",
11 | namespace: "http://namespace1",
12 | });
13 | nm.Add({
14 | prefix: "n2",
15 | namespace: "http://namespace2",
16 | });
17 | nm.Add({
18 | prefix: "n3",
19 | namespace: "http://namespace3",
20 | });
21 | });
22 |
23 | context("GetPrefix", () => {
24 | it("Empty", () => {
25 | const n = nm.GetPrefix("n4");
26 | assert.equal(!!n, false);
27 | });
28 | it("Exist", () => {
29 | const n = nm.GetPrefix("n2");
30 | assert.equal(!!n, true);
31 | assert.equal(n!.prefix, "n2");
32 | assert.equal(n!.namespace, "http://namespace2");
33 | });
34 | it("Start index more than list size", () => {
35 | const n = nm.GetPrefix("n3", 10);
36 | assert.equal(!!n, true);
37 | assert.equal(n!.prefix, "n3");
38 | assert.equal(n!.namespace, "http://namespace3");
39 | });
40 | });
41 |
42 | context("GetNamespace", () => {
43 | it("Empty", () => {
44 | const n = nm.GetNamespace("http://namespace4");
45 | assert.equal(!!n, false);
46 | });
47 | it("Exist", () => {
48 | const n = nm.GetNamespace("http://namespace3");
49 | assert.equal(!!n, true);
50 | assert.equal(n!.prefix, "n3");
51 | assert.equal(n!.namespace, "http://namespace3");
52 | });
53 | it("Start index more than list size", () => {
54 | const n = nm.GetNamespace("http://namespace3", 10);
55 | assert.equal(!!n, true);
56 | assert.equal(n!.prefix, "n3");
57 | assert.equal(n!.namespace, "http://namespace3");
58 | });
59 | });
60 |
61 | });
62 |
--------------------------------------------------------------------------------
/test/object.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { XmlAttribute, XmlElement } from "../src";
3 | import { XmlObject } from "../src";
4 |
5 | context("XmlObject", () => {
6 |
7 | it("IsEmpty", () => {
8 |
9 | @XmlElement({ localName: "test", namespaceURI: "http://some.com" })
10 | class Test extends XmlObject {
11 | @XmlAttribute()
12 | public Id: string;
13 | }
14 |
15 | const test = new Test();
16 |
17 | assert.equal(test.IsEmpty(), true);
18 | test.Id = "1";
19 | assert.equal(test.IsEmpty(), false);
20 |
21 | const xml = test.toString();
22 | const test2 = Test.LoadXml(xml);
23 | assert.equal(test2.IsEmpty(), false);
24 |
25 | });
26 |
27 | context("Get xml Element", () => {
28 | const xml = ``;
29 | let obj: Test;
30 |
31 | @XmlElement({
32 | localName: "root",
33 | })
34 | class Test extends XmlObject {
35 | }
36 | before(() => {
37 | obj = Test.LoadXml(xml);
38 | });
39 |
40 | context("GetElement", () => {
41 | it("required", () => {
42 | assert.throws(() => {
43 | obj.GetElement("NotExit", true);
44 | });
45 | });
46 | it("success", () => {
47 | const node = obj.GetElement("third");
48 | assert.equal(!!node, true);
49 | assert.equal(node!.nodeName, "third");
50 | });
51 | it("element not exist", () => {
52 | const obj2 = new Test();
53 | assert.throws(() => {
54 | obj2.GetElement("third");
55 | });
56 | });
57 | });
58 | context("GetChild", () => {
59 | it("required", () => {
60 | assert.throws(() => {
61 | obj.GetChild("NotExit", true);
62 | });
63 | });
64 | it("success", () => {
65 | const node = obj.GetChild("third");
66 | assert.equal(!!node, true);
67 | assert.equal(node!.nodeName, "third");
68 | });
69 | it("element not exist", () => {
70 | const obj2 = new Test();
71 | assert.throws(() => {
72 | obj2.GetChild("third");
73 | });
74 | });
75 | });
76 | context("GetChildren", () => {
77 | it("by name", () => {
78 | const list = obj.GetChildren("child");
79 | assert.equal(list.length, 2);
80 | });
81 | it("by name and namespace", () => {
82 | const list = obj.GetChildren("child", "html://n");
83 | assert.equal(list.length, 1);
84 | });
85 | it("element not exist", () => {
86 | const obj2 = new Test();
87 | assert.throws(() => {
88 | obj2.GetChildren("child");
89 | });
90 | });
91 | });
92 | context("GetFirstChild", () => {
93 | it("by name", () => {
94 | const node = obj.GetFirstChild("child");
95 | assert.equal(!!node, true);
96 | assert.equal(node!.localName, "child");
97 | });
98 | it("by name and namespace", () => {
99 | const node = obj.GetFirstChild("child", "html://n");
100 | assert.equal(!!node, true);
101 | assert.equal(node!.localName, "child");
102 | assert.equal(node!.namespaceURI, "html://n");
103 | });
104 | it("element not exist", () => {
105 | const obj2 = new Test();
106 | assert.throws(() => {
107 | obj2.GetFirstChild("child");
108 | });
109 | });
110 | });
111 | context("GetAttribute", () => {
112 | it("by name", () => {
113 | const attr = obj.GetAttribute("id", "2");
114 | assert.equal(attr, "0");
115 | });
116 | it("by default", () => {
117 | const attr = obj.GetAttribute("test", "3", false);
118 | assert.equal(attr, "3");
119 | });
120 | it("required", () => {
121 | assert.throws(() => {
122 | obj.GetAttribute("test", null, true);
123 | });
124 | });
125 | it("element not exist", () => {
126 | const obj2 = new Test();
127 | assert.throws(() => {
128 | obj2.GetAttribute("id", "4");
129 | });
130 | });
131 | });
132 | context("GetElementById", () => {
133 | it("id", () => {
134 | const el = obj.GetXml()!;
135 | const f = XmlObject.GetElementById(el, "1")!;
136 | assert.equal(f.localName, "first");
137 | });
138 | it("Id", () => {
139 | const el = obj.GetXml()!;
140 | const f = XmlObject.GetElementById(el, "2")!;
141 | assert.equal(f.localName, "second");
142 | });
143 | it("ID", () => {
144 | const el = obj.GetXml()!;
145 | const f = XmlObject.GetElementById(el, "3")!;
146 | assert.equal(f.localName, "third");
147 | });
148 | });
149 | });
150 |
151 | });
152 |
--------------------------------------------------------------------------------
/test/utils.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { assign, Parse, SelectNamespaces, SelectSingleNode, Stringify } from "../src";
3 |
4 | context("utils", () => {
5 |
6 | it("assign", () => {
7 | const obj = assign({}, { prop1: 1 }, { prop2: 2 }, { prop3: 3 }, { prop3: 4 });
8 |
9 | assert.equal(obj.prop1, 1);
10 | assert.equal(obj.prop2, 2);
11 | assert.equal(obj.prop3, 4);
12 | });
13 |
14 | context("SelectSingleNode", () => {
15 | it("Empty", () => {
16 | const xml = Parse("");
17 | const node = SelectSingleNode(xml, ".//second");
18 | assert.equal(node === null, true);
19 | });
20 | it("First element", () => {
21 | const xml = Parse(``);
22 | const node = SelectSingleNode(xml, ".//child") as Element;
23 | assert.equal(!!node, true);
24 | assert.equal(node!.attributes.length, 1);
25 | assert.equal(node!.attributes[0].value, "1");
26 | });
27 | });
28 |
29 | it("SelectNamespaces", () => {
30 | const xml = Parse(``);
31 | const namespaces = SelectNamespaces(xml.documentElement);
32 | assert.equal(Object.keys(namespaces).length, 3);
33 | assert.equal(namespaces[""], "html://namespace1");
34 | assert.equal(namespaces.n1, "html://namespace2");
35 | assert.equal(namespaces.n2, "html://namespace3");
36 | });
37 |
38 | it("Parse/Stringify", () => {
39 | const xmlString = ``;
40 | const xml = Parse(xmlString);
41 | const text = Stringify(xml);
42 | assert.equal(xmlString, text);
43 | });
44 |
45 | });
46 |
--------------------------------------------------------------------------------
/test/xml_collection.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert";
2 | import { XmlAttribute, XmlCollection, XmlElement, XmlObject } from "../src";
3 |
4 | context("XmlCollection", () => {
5 | @XmlElement({
6 | localName: "child",
7 | namespaceURI: "http://some.com",
8 | })
9 | class Child extends XmlObject {
10 | @XmlAttribute({ localName: "id", defaultValue: 0 })
11 | public Id: number;
12 |
13 | constructor(id?: number) {
14 | super();
15 |
16 | if (id !== void 0) {
17 | this.Id = id;
18 | }
19 | }
20 | }
21 |
22 | @XmlElement({
23 | localName: "children",
24 | namespaceURI: "http://some.com",
25 | parser: Child,
26 | })
27 | class Children extends XmlCollection {
28 | }
29 |
30 | it("Pop", () => {
31 | const col = new Children();
32 | col.Add(new Child(1));
33 | col.Add(new Child(2));
34 | col.Add(new Child(3));
35 | assert.equal(col.Count, 3);
36 | col.Pop();
37 | assert.equal(col.Count, 2);
38 | assert.equal(col.Item(0)!.Id, 1);
39 | assert.equal(col.Item(1)!.Id, 2);
40 | });
41 |
42 | it("RemoveAt", () => {
43 | const col = new Children();
44 | col.Add(new Child(1));
45 | col.Add(new Child(2));
46 | col.Add(new Child(3));
47 | assert.equal(col.Count, 3);
48 | col.RemoveAt(0);
49 | assert.equal(col.Count, 2);
50 | assert.equal(col.Item(0)!.Id, 2);
51 | assert.equal(col.Item(1)!.Id, 3);
52 | });
53 |
54 | it("Clear", () => {
55 | const col = new Children();
56 | col.Add(new Child(1));
57 | col.Add(new Child(2));
58 | col.Add(new Child(3));
59 | assert.equal(col.Count, 3);
60 | col.Clear();
61 | assert.equal(col.Count, 0);
62 | });
63 |
64 | it("IsEmpty", () => {
65 | const col = new Children();
66 | assert.equal(col.IsEmpty(), true);
67 | col.Add(new Child(1));
68 | assert.equal(col.IsEmpty(), false);
69 | });
70 |
71 | it("ForEach", () => {
72 | const col = new Children();
73 | col.Add(new Child(1));
74 | col.Add(new Child(2));
75 | col.Add(new Child(3));
76 |
77 | let id = 1;
78 | col.ForEach((item) => {
79 | assert.equal(item.Id, id++);
80 | });
81 | });
82 |
83 | it("Map", () => {
84 | const col = new Children();
85 | col.Add(new Child(1));
86 | col.Add(new Child(2));
87 | col.Add(new Child(3));
88 |
89 | let id = 1;
90 | col
91 | .Map((item) => {
92 | return item.Id;
93 | })
94 | .ForEach((item) => {
95 | assert.equal(item, id++);
96 | });
97 | });
98 |
99 | it("Filter", () => {
100 | const col = new Children();
101 | col.Add(new Child(1));
102 | col.Add(new Child(2));
103 | col.Add(new Child(3));
104 |
105 | const list = col.Filter((item) => {
106 | return item.Id === 3;
107 | });
108 | assert.equal(list.Count, 1);
109 | assert.equal(list.Item(0)!.Id, 3);
110 | });
111 |
112 | it("Sort", () => {
113 | const col = new Children();
114 | col.Add(new Child(1));
115 | col.Add(new Child(3));
116 | col.Add(new Child(2));
117 |
118 | const list = col.Sort((a, b) => {
119 | if (a.Id > b.Id) {
120 | return -1;
121 | }
122 | if (a.Id < b.Id) {
123 | return 1;
124 | }
125 | return 0;
126 | });
127 | assert.equal(list.Count, 3);
128 | assert.equal(list.Item(0)!.Id, 3);
129 | assert.equal(list.Item(1)!.Id, 2);
130 | assert.equal(list.Item(2)!.Id, 1);
131 | });
132 |
133 | it("Every", () => {
134 | const col = new Children();
135 | col.Add(new Child(1));
136 | col.Add(new Child(1));
137 | col.Add(new Child(1));
138 |
139 | assert.equal(col.Every((item) => {
140 | return item.Id === 1;
141 | }), true);
142 |
143 | col.Add(new Child(2));
144 | assert.equal(col.Every((item) => {
145 | return item.Id === 1;
146 | }), false);
147 | });
148 |
149 | context("HasChanged", () => {
150 |
151 | it("Initialized empty collection is not changed", () => {
152 | const col = new Children();
153 | assert.equal(col.HasChanged(), false);
154 | });
155 |
156 | it("Update state on item adding", () => {
157 | const col = new Children();
158 | col.Add(new Child());
159 | assert.equal(col.HasChanged(), true);
160 | });
161 |
162 | it("Update state on item removing", () => {
163 | const col = new Children();
164 | col.Add(new Child());
165 | col.GetXml();
166 | assert.equal(col.HasChanged(), false);
167 | col.RemoveAt(0);
168 | assert.equal(col.HasChanged(), true);
169 | });
170 |
171 | it("Set unchanged state for loaded XML", () => {
172 | const xml = ``;
173 | const col = new Children();
174 | col.LoadXml(xml);
175 | assert.equal(col.HasChanged(), false);
176 | assert.equal(col.Item(0)!.HasChanged(), false);
177 | });
178 |
179 | });
180 | });
181 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "strict": true,
6 | "outDir": "tmp",
7 | "strictPropertyInitialization": false,
8 | "esModuleInterop": true,
9 | "experimentalDecorators": true,
10 | "forceConsistentCasingInFileNames": true
11 | },
12 | "exclude": [
13 | "dist"
14 | ]
15 | }
--------------------------------------------------------------------------------
/tsconfig.types.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "declarationDir": "dist/types",
6 | "emitDeclarationOnly": true,
7 | "removeComments": false
8 | },
9 | "exclude": [ "dist", "test" ]
10 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "ban-types": false,
9 | "max-line-length": false,
10 | "prefer-for-of": false,
11 | "no-conditional-assignment": false,
12 | "no-reference": false,
13 | "no-var-requires": false,
14 | "object-literal-sort-keys": false,
15 | "object-literal-shorthand": false,
16 | "space-before-function-paren": false,
17 | "max-classes-per-file": false,
18 | "interface-name": false
19 | },
20 | "rulesDirectory": []
21 | }
--------------------------------------------------------------------------------