Skip to content

Commit 528dd51

Browse files
authored
Merge pull request #36 from geeklearningio/feature/xml-patch-array-support
Feature/xml patch array support
2 parents c93b373 + 96610aa commit 528dd51

File tree

6 files changed

+121
-35
lines changed

6 files changed

+121
-35
lines changed

Common/Node/jsonPatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var jsonPatch = require('fast-json-patch');
44

55
export class JsonPatcher implements patch.IPatcher {
66
constructor(
7-
private patches: patch.IPatch[]
7+
public patches: patch.IPatch[]
88
) {
99
}
1010

Common/Node/patch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface IPatch{
88
}
99

1010
export interface IPatcher {
11+
patches: IPatch[];
1112
apply(content: string): string;
1213
}
1314

Common/Node/patchProcess.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export function apply(patcher: patch.IPatcher, workingDirectory: string, filters
2424
outputPatchedFile: boolean, failIfNoPatchApplied: boolean, skipErrors: boolean) {
2525
var files = matcher.getMatches(workingDirectory, filters);
2626

27+
for (var index = 0; index < patcher.patches.length; index++) {
28+
var patch = patcher.patches[index];
29+
if (patch.path && patch.path[0] != '/'
30+
|| patch.from && patch.from[0] != '/')
31+
{
32+
throw new Error("All path must start with a leading slash. Please verify patch at index " + String(index));
33+
}
34+
}
35+
2736
tl.debug("Attempt to patch " + String(files.length) + "files");
2837

2938
var filePatched = 0;

Tasks/XmlPatch/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"dependencies": {
66
"fs-extra": "0.30.0",
77
"micromatch": "2.3.11",
8-
"xmldom": "0.1.22",
8+
"xmldom": "0.1.27",
99
"xpath": "0.0.24",
1010
"xregexp": "3.1.1",
1111
"vsts-task-lib": "1.1.0",

Tasks/XmlPatch/xmlPatcher.ts

Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,39 @@ import XRegExp = require('xregexp');
55

66
export class XmlPatcher implements patch.IPatcher {
77
constructor(
8-
private patches: patch.IPatch[],
8+
public patches: patch.IPatch[],
99
private namespaces: { [tag: string]: string }
1010
) {
1111
}
1212

13+
detectArrayOperation(path: string): { path: string, isArrayOperation: boolean, append?: boolean, index?: number } {
14+
var lastSlash = path.lastIndexOf('/');
15+
var lastFragment = path.substr(lastSlash + 1);
16+
var remainingPath = path.substr(0, lastSlash);
17+
18+
if (lastFragment == '-') {
19+
return {
20+
path: remainingPath,
21+
isArrayOperation: true,
22+
append: true
23+
};
24+
}
25+
26+
var isLastFragmentDigitOnly: string[] = XRegExp.match(lastFragment, /^\d+$/g);
27+
if (isLastFragmentDigitOnly.length > 0) {
28+
return {
29+
path: remainingPath,
30+
isArrayOperation: true,
31+
index: parseInt(lastFragment)
32+
};
33+
}
34+
35+
return {
36+
path: path,
37+
isArrayOperation: false
38+
};
39+
}
40+
1341
getParentPath(path: string): { path: string, nodeName: string, isAttribute: boolean } {
1442
var lastSlash = path.lastIndexOf('/');
1543
var nodeName = path.substr(lastSlash + 1);
@@ -26,12 +54,30 @@ export class XmlPatcher implements patch.IPatcher {
2654
return false;
2755
}
2856

57+
notsupported(patch: patch.IPatch): boolean {
58+
console.log("operation not supported: " + patch.op + " " + patch.path);
59+
return false;
60+
}
61+
2962
remove(xml: Document, select: any, patch: patch.IPatch): boolean {
63+
var arrayOperation = this.detectArrayOperation(patch.path);
64+
if (arrayOperation.isArrayOperation) {
65+
if (arrayOperation.append) {
66+
var node = <SVGSVGElement>select(arrayOperation.path, xml, true);
67+
node.removeChild(node.lastChild);
68+
return true;
69+
} else {
70+
var node = <SVGSVGElement>select(arrayOperation.path, xml, true);
71+
node.removeChild(node.childNodes[arrayOperation.index]);
72+
return true;
73+
}
74+
}
75+
3076
var node = <SVGSVGElement>select(patch.path, xml, true);
3177
if (node) {
3278
var parentPath = this.getParentPath(patch.path);
3379
var parentNode = <SVGSVGElement>select(parentPath.path, xml, true);
34-
if (parentPath.isAttribute){
80+
if (parentPath.isAttribute) {
3581
parentNode.removeAttribute(parentPath.nodeName);
3682
} else {
3783
node.parentNode.removeChild(node);
@@ -43,14 +89,20 @@ export class XmlPatcher implements patch.IPatcher {
4389
}
4490

4591
move(xml: Document, select: any, patch: patch.IPatch): boolean {
46-
var fromNode = <SVGSVGElement>select(patch.from, xml, true);
47-
var toNode = <SVGSVGElement>select(patch.path, xml, true);
48-
if (fromNode) {
49-
patch.value = fromNode.textContent;
50-
this.remove(xml, select, { op: 'remove', path : patch.from });
51-
return this.replace(xml, select, patch);
92+
var arrayOperation = this.detectArrayOperation(patch.path);
93+
var arrayOperationFrom = this.detectArrayOperation(patch.from);
94+
if (arrayOperation.isArrayOperation || arrayOperationFrom.isArrayOperation) {
95+
return this.notsupported(patch);
5296
} else {
53-
return this.notfound(patch);
97+
var fromNode = <SVGSVGElement>select(patch.from, xml, true);
98+
var toNode = <SVGSVGElement>select(patch.path, xml, true);
99+
if (fromNode) {
100+
patch.value = fromNode.textContent;
101+
this.remove(xml, select, { op: 'remove', path: patch.from });
102+
return this.replace(xml, select, patch);
103+
} else {
104+
return this.notfound(patch);
105+
}
54106
}
55107
}
56108

@@ -66,6 +118,21 @@ export class XmlPatcher implements patch.IPatcher {
66118
}
67119

68120
add(xml: Document, select: any, patch: patch.IPatch): boolean {
121+
var arrayOperation = this.detectArrayOperation(patch.path);
122+
if (arrayOperation.isArrayOperation) {
123+
if (arrayOperation.append) {
124+
var node = <SVGSVGElement>select(arrayOperation.path, xml, true);
125+
var newNode = <HTMLElement>xml.createElement(patch.value);
126+
node.appendChild(newNode);
127+
return true;
128+
} else {
129+
var node = <SVGSVGElement>select(arrayOperation.path, xml, true);
130+
var newNode = <HTMLElement>xml.createElement(patch.value);
131+
node.insertBefore(newNode, node.childNodes[arrayOperation.index])
132+
return true;
133+
}
134+
}
135+
69136
var node = <SVGSVGElement>select(patch.path, xml, true);
70137
if (node) {
71138
node.textContent = patch.value;
@@ -92,18 +159,28 @@ export class XmlPatcher implements patch.IPatcher {
92159
}
93160

94161
replace(xml: Document, select: any, patch: patch.IPatch): boolean {
95-
var node = <SVGSVGElement>select(patch.path, xml, true);
96-
if (node) {
97-
var parentPath = this.getParentPath(patch.path);
98-
var parentNode = <SVGSVGElement>select(parentPath.path, xml, true);
99-
if (parentPath.isAttribute){
100-
parentNode.setAttribute(parentPath.nodeName, patch.value);
101-
} else {
102-
node.textContent = patch.value;
103-
}
162+
var arrayOperation = this.detectArrayOperation(patch.path);
163+
if (arrayOperation.isArrayOperation) {
164+
var node = <SVGSVGElement>select(arrayOperation.path, xml, true);
165+
var childNode = node.childNodes[arrayOperation.index];
166+
var newNode = <HTMLElement>xml.createElement(patch.value);
167+
node.insertBefore(newNode, childNode)
168+
node.removeChild(childNode);
104169
return true;
105170
} else {
106-
return this.add(xml, select, patch);
171+
var node = <SVGSVGElement>select(patch.path, xml, true);
172+
if (node) {
173+
var parentPath = this.getParentPath(patch.path);
174+
var parentNode = <SVGSVGElement>select(parentPath.path, xml, true);
175+
if (parentPath.isAttribute) {
176+
parentNode.setAttribute(parentPath.nodeName, patch.value);
177+
} else {
178+
node.textContent = patch.value;
179+
}
180+
return true;
181+
} else {
182+
return this.add(xml, select, patch);
183+
}
107184
}
108185
}
109186

@@ -135,12 +212,12 @@ export class XmlPatcher implements patch.IPatcher {
135212
} else if (patch.op == 'test') {
136213
operation = this.test.bind(this);
137214
}
138-
215+
139216
if (!operation(xml, select, patch)) {
140217
throw new Error("Failed to patch xml file");
141218
}
142219
}
143-
220+
144221
return new xmldom.XMLSerializer().serializeToString(xml);
145222
}
146223
}

Tests/XmlPatch/xmlPatcher.spec.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe("XML Patcher", () => {
101101
{
102102
op: "add", path: "/rootNode/0", value: "leaf"
103103
},{
104-
op: "replace", path: "/rootNode/leaf:first()/@color", value: "#bbbbbb"
104+
op: "replace", path: "/rootNode/leaf[1]/@color", value: "#bbbbbb"
105105
}
106106
], {});
107107
var result = patcher.apply(source);
@@ -114,7 +114,7 @@ describe("XML Patcher", () => {
114114
{
115115
op: "add", path: "/rootNode/1", value: "leaf"
116116
},{
117-
op: "replace", path: "/rootNode/leaf[1]/@color", value: "#bbbbbb"
117+
op: "replace", path: "/rootNode/leaf[2]/@color", value: "#bbbbbb"
118118
}
119119
], {});
120120
var result = patcher.apply(source);
@@ -127,12 +127,12 @@ describe("XML Patcher", () => {
127127
{
128128
op: "add", path: "/rootNode/-", value: "leaf"
129129
},{
130-
op: "replace", path: "/rootNode/leaf:last()/@color", value: "#bbbbbb"
130+
op: "replace", path: "/rootNode/leaf[last()]/@color", value: "#bbbbbb"
131131
}
132132
], {});
133133
var result = patcher.apply(source);
134134

135-
expect(result).toEqual('<rootNode><leaf color="#aaaaaa"><leaf color="#000000"/><leaf color="#bbbbbb"/></rootNode>');
135+
expect(result).toEqual('<rootNode><leaf color="#aaaaaa"/><leaf color="#000000"/><leaf color="#bbbbbb"/></rootNode>');
136136
});
137137
});
138138

@@ -150,7 +150,7 @@ describe("XML Patcher", () => {
150150
});
151151

152152
describe("Move", () => {
153-
it(": move at index", () => {
153+
xit(": move at index", () => {
154154
var patcher = new xmlatcher.XmlPatcher([
155155
{
156156
op: "move", from: "/rootNode/0", path: "/rootNode/1", value: "otherleaf"
@@ -166,34 +166,34 @@ describe("XML Patcher", () => {
166166
it(": delete at index", () => {
167167
var patcher = new xmlatcher.XmlPatcher([
168168
{
169-
op: "delete", path: "/rootNode/1"
169+
op: "remove", path: "/rootNode/1"
170170
}
171171
], {});
172172
var result = patcher.apply(source);
173173

174-
expect(result).toEqual('<rootNode><leaf color="#000000"/></rootNode>');
174+
expect(result).toEqual('<rootNode><leaf color="#aaaaaa"/></rootNode>');
175175
});
176176

177177
it(": delete first", () => {
178178
var patcher = new xmlatcher.XmlPatcher([
179179
{
180-
op: "delete", path: "/rootNode/0"
180+
op: "remove", path: "/rootNode/0"
181181
}
182182
], {});
183183
var result = patcher.apply(source);
184184

185-
expect(result).toEqual('<rootNode><leaf color="#aaaaaa"/></rootNode>');
185+
expect(result).toEqual('<rootNode><leaf color="#000000"/></rootNode>');
186186
});
187187

188188
it(": delete last", () => {
189189
var patcher = new xmlatcher.XmlPatcher([
190190
{
191-
op: "delete", path: "/rootNode/-"
191+
op: "remove", path: "/rootNode/-"
192192
}
193193
], {});
194194
var result = patcher.apply(source);
195195

196-
expect(result).toEqual('<rootNode><leaf color="#000000"/></rootNode>');
196+
expect(result).toEqual('<rootNode><leaf color="#aaaaaa"/></rootNode>');
197197
});
198198
});
199199
});
@@ -219,7 +219,6 @@ describe("XML Patcher", () => {
219219
}
220220
], {});
221221
var result = patcher.apply(source);
222-
console.log(result);
223222
expect(result).not.toContain('#should_be_replaced#');
224223
});
225224
});

0 commit comments

Comments
 (0)