├── .gitignore
├── .travis.yml
├── CHANGES.txt
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── build.gradle
├── examples
└── gradle
│ ├── build.gradle
│ ├── config
│ └── xml-patches
│ │ ├── multi
│ │ ├── included-multi-patch.xml
│ │ └── resources-multi-patch.xml
│ │ └── single
│ │ ├── first-patch.xml
│ │ └── second-patch.xml
│ └── src
│ └── main
│ ├── example-documents
│ ├── dont-patch-me.xml
│ ├── patch-me-twice.xml
│ └── patch-me.xml
│ └── resources
│ └── com
│ └── github
│ └── dnault
│ └── xmlpatch
│ └── example
│ └── patch-me.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
├── main
├── groovy
│ └── com
│ │ └── github
│ │ └── dnault
│ │ └── xmlpatch
│ │ └── filter
│ │ └── multi
│ │ ├── XmlMultiPatch.java
│ │ └── XmlPatchSpec.groovy
├── java
│ └── com
│ │ └── github
│ │ └── dnault
│ │ └── xmlpatch
│ │ ├── BatchPatcher.java
│ │ ├── CommandLineDriver.java
│ │ ├── ErrorCondition.java
│ │ ├── PatchException.java
│ │ ├── Patcher.java
│ │ ├── ant
│ │ ├── AbstractPatchTask.java
│ │ ├── AntXmlPatchFilter.java
│ │ ├── PatchXmlDir.java
│ │ └── PatchXmlFile.java
│ │ ├── batch
│ │ ├── AssembledPatch.java
│ │ └── PatchAssembler.java
│ │ ├── filter
│ │ ├── XmlPatch.java
│ │ └── multi
│ │ │ └── XmlPatchOptions.java
│ │ └── internal
│ │ ├── DeferredInitFilterReader.java
│ │ ├── IoHelper.java
│ │ ├── Log.java
│ │ ├── XmlHelper.java
│ │ └── logging
│ │ ├── ConsoleLogger.java
│ │ ├── Slf4jLogger.java
│ │ └── XmlPatchLogger.java
└── resources
│ ├── META-INF
│ └── xml-patch
│ │ ├── commons-io-LICENSE.txt
│ │ ├── commons-io-NOTICE.txt
│ │ ├── jaxen-LICENSE.txt
│ │ ├── jdom-LICENSE.txt
│ │ ├── jdom-info.xml
│ │ └── joptsimple-LICENSE.txt
│ └── com
│ └── github
│ └── dnault
│ └── xmlpatch
│ └── antlib.xml
└── test
├── java
└── com
│ └── github
│ └── dnault
│ └── xmlpatch
│ ├── filter
│ └── multi
│ │ └── XmlMultiPatchTest.java
│ └── test
│ ├── AddAttributeTest.java
│ ├── AddContentTest.java
│ ├── DataDrivenTest.java
│ ├── ReplaceNamespaceTest.java
│ ├── Rfc7351Test.java
│ ├── TestHelper.java
│ └── XmlHelperTest.java
└── resources
└── com
└── github
└── dnault
└── xmlpatch
└── test
└── regression-test.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build
3 | out
4 | .DS_Store
5 | .idea
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
4 |
5 | before_cache:
6 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
7 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
8 | cache:
9 | directories:
10 | - $HOME/.gradle/caches/
11 | - $HOME/.gradle/wrapper/
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | Thank you to the kind folks who have contributed pull requests, bug reports, and ideas for enhancements.
4 |
5 |
6 | Relase 0.3.0:
7 | * Issue #6 Create standalone runnable JAR -- Suggested by Maurizio Pillitu (maoo)
8 | * Issue #5 Switch to jdom2 -- Contributed by Norbert Kiesel (nkiesel)
9 | * Issue #4 Upgraded gradle, gradle plugins, and jars -- Contributed by Norbert Kiesel (nkiesel)
10 |
11 | Relase 0.2.0:
12 | * Issue #2 Allow "patch" root element in accordance with RFC 7351 -- Suggested by Jonathan Buhacoff (jbuhacoff)
13 |
14 | Release 0.1.0:
15 | * Initial release
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 |
2 | The standalone distribution of xml-patch includes repackaged libraries developed by third parties.
3 | Here's a big THANK YOU to the developers of:
4 |
5 | ----
6 |
7 | JDOM
8 | Copyright 2000-2015 Jason Hunter
9 |
10 | LICENSE: JDOM License (BSD-like)
11 |
12 | ----
13 |
14 | Jaxen
15 | Copyright 2003-2006 The Werken Company. All Rights Reserved.
16 |
17 | LICENSE: BSD 3-Clause
18 |
19 | ----
20 |
21 | joptsimple
22 | Copyright 2004-2015 Paul R. Holser, Jr.
23 |
24 | LICENSE: MIT License.
25 |
26 | ----
27 |
28 | Apache Commons IO
29 | Copyright 2002-2012 The Apache Software Foundation
30 |
31 | This product includes software developed by
32 | The Apache Software Foundation (http://www.apache.org/).
33 |
34 | LICENSE: Apache License 2.0
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xml-patch
2 |
3 | [](https://travis-ci.org/dnault/xml-patch)
4 | [](http://www.apache.org/licenses/LICENSE-2.0)
5 | 
6 |
7 | Java implementation of RFC 5261: An XML Patch Operations Framework Utilizing XPath Selectors
8 |
9 | Also compatible with RFC 7351 patch documents.
10 |
11 | Featuring Gradle and Ant integration.
12 |
13 | # Maven Coordinates
14 |
15 | ```xml
16 |
17 | com.github.dnault
18 | xml-patch
19 | 0.3.1
20 |
21 | ```
22 |
23 | You can also [download the JAR](https://jcenter.bintray.com/com/github/dnault/xml-patch/0.3.1/xml-patch-0.3.1.jar)
24 | if you want to run the patcher from the command line.
25 |
26 |
27 | ## Introduction
28 |
29 | So you're interested in patching some XML documents? Here's a quick illustration of why RFC 5261 XML patches
30 | are the way to go.
31 |
32 | Original document:
33 |
34 | ```xml
35 |
36 |
37 | Hello sorrow
38 |
39 | ```
40 |
41 |
42 | RFC 5261 patch:
43 | ```xml
44 |
45 | https://tools.ietf.org/html/rfc5261
46 | Goodbye sorrow
47 |
48 | Hello XML patch
49 |
50 | ```
51 |
52 | Result of applying the patch to the original document:
53 | ```xml
54 |
55 |
56 | Goodbye sorrow
57 | Hello XML patch
58 |
59 | ```
60 |
61 | Check out [RFC 5261](https://tools.ietf.org/html/rfc5261) for more examples
62 | and a detailed explanation of the patch language.
63 |
64 |
65 | ## Multi-Patch
66 |
67 | RFC 5261 is great for patching one document at a time. If you need to patch many documents,
68 | it may be convienient to bundle related patches into a 'multi-patch' document that can target
69 | multiple, specific files.
70 |
71 |
72 | Original file 'pets.xml':
73 |
74 | ```xml
75 |
76 |
77 |
78 |
79 | ```
80 |
81 | Original file 'stores.xml':
82 |
83 | ```xml
84 |
85 |
86 |
87 | ```
88 |
89 | Multi-patch:
90 |
91 | ```xml
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | ```
104 |
105 | As you can see, a multi-patch bundles zero or more RFC 5261 `diff` elements into a single document.
106 | Each `diff` element has a `file` attribute specifying a relative path to the file targeted by the patch.
107 |
108 | If your project configuration is spread across multiple XML files (web.xml, log4j.xml, foo.xml, etc.)
109 | then a multi-patch is a convenient way to patch all of these files at once.
110 |
111 |
112 | ### Multi-Patch Composition
113 |
114 | Multi-patches may include other multi-patches. This is useful if your project has multiple configurations
115 | that share some common patches.
116 |
117 |
118 | Multipatch file 'common-multi-patch.xml':
119 | ```xml
120 |
121 |
122 |
123 |
124 |
125 | ```
126 |
127 | Multipatch file 'testing-multi-patch.xml':
128 | ```xml
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | ```
137 |
138 | Multipatch file 'production-multi-patch.xml':
139 | ```xml
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | ```
148 |
149 | ## Using the patch tool
150 |
151 | The xml-patch library may invoked from the command line,
152 | or from build tools like Gradle or Ant.
153 |
154 |
155 | ### Gradle
156 |
157 | XML patching is implemented using Gradle's built-in support for custom filters.
158 | Any task implementing CopySpec can be augmented to perform XML patching.
159 |
160 | File 'build.gradle':
161 |
162 | ```groovy
163 | buildscript {
164 | repositories {
165 | jcenter()
166 | }
167 |
168 | dependencies {
169 | classpath "com.github.dnault:xml-patch:0.2.0"
170 | }
171 | }
172 |
173 | import com.github.dnault.xmlpatch.filter.XmlPatch
174 | import com.github.dnault.xmlpatch.filter.multi.XmlMultiPatch
175 | import com.github.dnault.xmlpatch.filter.multi.XmlPatchSpec
176 |
177 | apply plugin: 'java'
178 |
179 | ext {
180 | patchDir = "config/xml-patches"
181 | }
182 |
183 | // Copy a directory, selectively applying a single-target patch.
184 | //
185 | // Use this single-target technique if you're only patching a few documents
186 | // and don't require multi-patch composition.
187 | //
188 | task singleTargetPatchExample(type: Copy) {
189 | inputs.dir patchDir
190 |
191 | from "src/main/example-documents"
192 | into "${buildDir}/patched"
193 |
194 | // Use one 'filesMatching' statement for each file you want to patch.
195 |
196 | filesMatching("**/patch-me.xml") {
197 | // Single-target patches use the 'XmlPatch' filter.
198 | filter(XmlPatch, patch: "${patchDir}/single/first-patch.xml")
199 | }
200 |
201 | filesMatching("**/patch-me-twice.xml") {
202 | // Multiple filters may be applied in sequence.
203 | filter(XmlPatch, patch: "${patchDir}/single/first-patch.xml")
204 | filter(XmlPatch, patch: "${patchDir}/single/second-patch.xml")
205 | }
206 | }
207 |
208 |
209 | // Augment the standard 'processResources' task to apply a multi-patch
210 | // to classpath resources. The same technique could be applied to the 'war' task
211 | // to patch webapp documents.
212 | //
213 | // Use this multi-patch technique if you're patching many documents or want
214 | // to take advantage of multi-patch composition.
215 | //
216 | processResources {
217 | inputs.dir patchDir
218 |
219 | // To apply a multi-patch, first create a patch specification.
220 | def resourcesPatch = new XmlPatchSpec("${patchDir}/multi/resources-multi-patch.xml")
221 |
222 | filesMatching("**/*.xml") {
223 | // Multi-patches use the 'XMlMultiPatch' filter.
224 | // The filter will only process the file if the 'path' specified here
225 | // matches the 'file' attribute of a in the multi-patch document.
226 | filter(XmlMultiPatch, spec: resourcesPatch, path: it.path)
227 | }
228 |
229 | // Optionally assert that each file targeted by the patch was seen by the filter.
230 | doLast {
231 | resourcesPatch.done()
232 | }
233 | }
234 | ```
235 |
236 |
237 | ### Command Line
238 |
239 | You'll need the JAR for this. You can grab it from the download link in the
240 | **Maven Coordinates** section above.
241 |
242 | Apply an RFC 5261 patch:
243 |
244 | java -jar xml-patch-.jar
245 |
246 | A dash (-) may be used to indicate standard input / output.
247 |
248 |
249 | # Credits
250 |
251 | This library includes the following software, repackaged to avoid dependency conflicts:
252 |
253 | * JDOM (JDOM License, BSD-like) Copyright 2000-2015 Jason Hunter
254 | * Jaxen (BSD 3-Clause) Copyright 2003-2006 The Werken Company. All Rights Reserved.
255 | * joptsimple (MIT License) Copyright 2004-2015 Paul R. Holser, Jr.
256 | * Apache Commons IO (Apache License 2.0) Copyright 2002-2012 The Apache Software Foundation
257 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "java"
3 | id "groovy"
4 | id "maven-publish"
5 | id "application"
6 | id 'com.github.johnrengelman.plugin-shadow' version '2.0.2'
7 | id "com.jfrog.bintray" version "1.5"
8 | }
9 |
10 | group = "com.github.dnault"
11 | version = "0.3.2-SNAPSHOT"
12 |
13 | repositories {
14 | jcenter()
15 | }
16 |
17 | tasks.withType(Tar) {
18 | compression = Compression.GZIP
19 | }
20 |
21 | targetCompatibility = "1.7"
22 | sourceCompatibility = "1.7"
23 |
24 | mainClassName = "com.github.dnault.xmlpatch.CommandLineDriver"
25 |
26 | jar {
27 | manifest {
28 | attributes("Main-Class": mainClassName)
29 | }
30 | }
31 |
32 | dependencies {
33 | shadow gradleApi()
34 | shadow localGroovy()
35 | compileOnly "org.apache.ant:ant:1.9.6"
36 |
37 | compile "org.jdom:jdom2:2.0.6"
38 | compile "jaxen:jaxen:1.1.6"
39 | compile "commons-io:commons-io:2.4"
40 | compile "net.sf.jopt-simple:jopt-simple:4.9"
41 |
42 | testCompile "junit:junit:4.12"
43 | }
44 |
45 | shadowJar {
46 | classifier = null
47 |
48 | ['org.apache.commons', 'org.jaxen', 'org.jdom2', 'joptsimple'].each {
49 | relocate it, "com.github.dnault.xmlpatch.repackaged.${it}"
50 | }
51 |
52 | // JAXEN-209 Jaxen includes a duplicate, obsolete version of this class not required by modern JDKs
53 | exclude 'org/w3c/dom/UserDataHandler.class'
54 |
55 | exclude 'META-INF/maven/**'
56 | exclude 'META-INF/*.txt'
57 | exclude 'META-INF/*.xml'
58 | }
59 |
60 | task sourceJar(type: Jar, dependsOn: classes) {
61 | from sourceSets.main.allSource
62 | }
63 |
64 | task javadocJar(type: Jar, dependsOn: javadoc) {
65 | from javadoc.destinationDir
66 | }
67 |
68 | def gitUrl = 'git@github.com:dnault/xml-patch.git';
69 | ext.description = "Java implementation of RFC 5261: An XML Patch Operations Framework Utilizing XPath Selectors"
70 | def pomConfig = {
71 | name "${group}:${project.name}"
72 | description project.ext.description
73 | url 'https://github.com/dnault/xml-patch'
74 | scm {
75 | url "${gitUrl}"
76 | connection "scm:git:${gitUrl}"
77 | developerConnection "scm:git:${gitUrl}"
78 | }
79 | licenses {
80 | license {
81 | name 'Apache License 2.0'
82 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
83 | }
84 | }
85 | developers {
86 | developer {
87 | name 'David Nault'
88 | email 'dnault@mac.com'
89 | organization 'dnault'
90 | organizationUrl 'https://github.com/dnault'
91 | }
92 | }
93 | }
94 |
95 | task install {
96 | dependsOn(publishToMavenLocal)
97 | }
98 |
99 | publishing {
100 | publications {
101 | mavenJava(MavenPublication) { publication ->
102 | project.shadow.component(publication)
103 |
104 | pom.withXml {
105 | asNode().appendNode('description', project.ext.description)
106 | asNode().children().last() + pomConfig
107 | }
108 |
109 | artifact sourceJar {
110 | classifier 'sources'
111 | }
112 |
113 | artifact javadocJar {
114 | classifier 'javadoc'
115 | }
116 | }
117 | }
118 | }
119 |
120 | bintrayUpload.doFirst {
121 | if (version.contains("SNAPSHOT")) {
122 | throw new RuntimeException("Must not upload snapshots to bintray (current version is ${version}) -- create and tag a release version first!")
123 | }
124 | }
125 |
126 | bintray {
127 |
128 | if (project.hasProperty('bintrayUsername')) {
129 | user = project.bintrayUsername
130 | }
131 |
132 | if (project.hasProperty('bintrayApiKey')) {
133 | key = project.bintrayApiKey
134 | }
135 |
136 | publications = ['mavenJava'] // When uploading Maven-based publication files
137 |
138 | dryRun = false //false //Whether to run this as dry-run, without deploying
139 | publish = false //true //If version should be auto published after an upload
140 |
141 | pkg {
142 | repo = 'maven'
143 | // userOrg = 'myorg' //An optional organization name when the repo belongs to one of the user's orgs
144 | name = 'xml-patch'
145 | desc = 'Java implementation of RFC 5261: An XML Patch Operations Framework Utilizing XPath Selectors'
146 | websiteUrl = 'https://github.com/dnault/xml-patch'
147 | issueTrackerUrl = 'https://github.com/dnault/xml-patch/issues'
148 | vcsUrl = 'https://github.com/dnault/xml-patch.git'
149 | licenses = ['Apache-2.0']
150 | labels = ['xml', 'patch']
151 | publicDownloadNumbers = false//true
152 | //attributes= ['a': ['ay1', 'ay2'], 'b': ['bee'], c: 'cee'] //Optional package-level attributes
153 | //Optional version descriptor
154 | version {
155 | name = project.version //'1.3-Final' //Bintray logical version name
156 | //desc = 'optional, version-specific description'
157 | released = new Date() //'optional, date of the version release' //2 possible values: date in the format of 'yyyy-MM-dd'T'HH:mm:ss.SSSZZ' OR a java.util.Date instance
158 | vcsTag = project.version //'1.3.0'
159 | // attributes = ['gradle-plugin': 'com.use.less:com.use.less.gradle:gradle-useless-plugin'] //Optional version-level attributes
160 | gpg {
161 | // sign = true //Determines whether to GPG sign the files. The default is false
162 | // passphrase = 'passphrase' //Optional. The passphrase for GPG signing'
163 | }
164 | mavenCentralSync {
165 | sync = false //true //Optional (true by default). Determines whether to sync the version to Maven Central.
166 | user = 'userToken' //OSS user token
167 | password = 'paasword' //OSS user password
168 | close = '0'//'1' //Optional property. By default the staging repository is closed and artifacts are released to Maven Central. You can optionally turn this behaviour off (by puting 0 as value) and release the version manually.
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/examples/gradle/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 |
6 | dependencies {
7 | classpath "com.github.dnault:xml-patch:0.3.1"
8 | }
9 | }
10 |
11 | import com.github.dnault.xmlpatch.filter.XmlPatch
12 | import com.github.dnault.xmlpatch.filter.multi.XmlMultiPatch
13 | import com.github.dnault.xmlpatch.filter.multi.XmlPatchSpec
14 |
15 | apply plugin: 'java'
16 |
17 | ext {
18 | patchDir = "config/xml-patches"
19 | }
20 |
21 | // Copy a directory, selectively applying a single-target patch.
22 | //
23 | // Use this single-target technique if you're only patching a few documents
24 | // and don't require multi-patch composition.
25 | //
26 | task singleTargetPatchExample(type: Copy) {
27 | inputs.dir patchDir
28 |
29 | from "src/main/example-documents"
30 | into "${buildDir}/patched"
31 |
32 | // Use one 'filesMatching' statement for each file you want to patch.
33 |
34 | filesMatching("**/patch-me.xml") {
35 | // Single-target patches use the 'XmlPatch' filter.
36 | filter(XmlPatch, patch: "${patchDir}/single/first-patch.xml")
37 | }
38 |
39 | filesMatching("**/patch-me-twice.xml") {
40 | // Multiple filters may be applied in sequence.
41 | filter(XmlPatch, patch: "${patchDir}/single/first-patch.xml")
42 | filter(XmlPatch, patch: "${patchDir}/single/second-patch.xml")
43 | }
44 | }
45 |
46 | // Augment the standard 'processResources' task to apply a multi-patch
47 | // to classpath resources. The same technique could be applied to the 'war' task
48 | // to patch webapp documents.
49 | //
50 | // Use this multi-patch technique if you're patching many documents or want
51 | // to take advantage of multi-patch composition.
52 | //
53 | processResources {
54 | inputs.dir patchDir
55 |
56 | // To apply a multi-patch, first create a patch specification.
57 | def resourcesPatch = new XmlPatchSpec("${patchDir}/multi/resources-multi-patch.xml")
58 |
59 | filesMatching("**/*.xml") {
60 | // Multi-patches use the 'XMlMultiPatch' filter.
61 | // The filter will only process the file if the 'path' specified here
62 | // matches the 'file' attribute of a in the multi-patch document.
63 | filter(XmlMultiPatch, spec: resourcesPatch, path: it.path)
64 | }
65 |
66 | // Optionally assert that each file targeted by the patch was seen by the filter.
67 | doLast {
68 | resourcesPatch.done()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/examples/gradle/config/xml-patches/multi/included-multi-patch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/examples/gradle/config/xml-patches/multi/resources-multi-patch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/gradle/config/xml-patches/single/first-patch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/examples/gradle/config/xml-patches/single/second-patch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/examples/gradle/src/main/example-documents/dont-patch-me.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/gradle/src/main/example-documents/patch-me-twice.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/gradle/src/main/example-documents/patch-me.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/gradle/src/main/resources/com/github/dnault/xmlpatch/example/patch-me.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnault/xml-patch/65828e2d4ef5b9ceea013111b632937d2b49cb33/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 08 17:50:13 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/src/main/groovy/com/github/dnault/xmlpatch/filter/multi/XmlMultiPatch.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.filter.multi;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.io.Reader;
7 |
8 | import com.github.dnault.xmlpatch.filter.XmlPatch;
9 | import com.github.dnault.xmlpatch.internal.Log;
10 | import com.github.dnault.xmlpatch.batch.AssembledPatch;
11 | import com.github.dnault.xmlpatch.internal.DeferredInitFilterReader;
12 | import org.apache.commons.io.FileUtils;
13 | import org.jdom2.Element;
14 | import org.jdom2.output.Format;
15 | import org.jdom2.output.XMLOutputter;
16 |
17 | public class XmlMultiPatch extends DeferredInitFilterReader {
18 | XmlPatchSpec spec;
19 | String path;
20 |
21 | public XmlMultiPatch(Reader in) {
22 | super(in);
23 | }
24 |
25 | @Override
26 | protected void initialize() {
27 | if (spec == null) {
28 | throw new RuntimeException("missing 'spec' parameter, value must be XmlPatchSpec instance");
29 | }
30 |
31 | if (path == null) {
32 | throw new RuntimeException("missing 'path' parameter, relative path to file being patched");
33 | }
34 |
35 | try {
36 | in = buildFilterChain(in, path, spec.resolve());
37 | } catch (IOException e) {
38 | throw new RuntimeException(e);
39 | }
40 | }
41 |
42 | protected Reader buildFilterChain(Reader source, String sourcePath, AssembledPatch patch) throws IOException {
43 | for (Element diff : patch.getDiffs(sourcePath)) {
44 | diff = (Element) diff.clone();
45 | diff.removeAttribute("file");
46 |
47 | final File tempDiff = saveToTempFile(diff);
48 |
49 | source = new XmlPatch(source) {
50 | {
51 | setPatch(tempDiff.getAbsolutePath());
52 | }
53 |
54 | @Override
55 | public void close() throws IOException {
56 | super.close();
57 | FileUtils.deleteQuietly(tempDiff);
58 | }
59 | };
60 | }
61 |
62 | return source;
63 | }
64 |
65 | private File saveToTempFile(Element diff) throws IOException {
66 | Format format = Format.getRawFormat();
67 | format.setOmitDeclaration(true);
68 | XMLOutputter outputter = new XMLOutputter(format);
69 |
70 | File temp = File.createTempFile("xml-patch-diff-", ".xml");
71 | try (FileOutputStream os = new FileOutputStream(temp)) {
72 | outputter.output(diff, os);
73 | }
74 |
75 | Log.debug("created temp file: " + temp.getAbsolutePath());
76 |
77 | return temp;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/groovy/com/github/dnault/xmlpatch/filter/multi/XmlPatchSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.filter.multi
2 |
3 | import com.github.dnault.xmlpatch.batch.AssembledPatch
4 | import org.gradle.api.file.FileCollection
5 | import org.gradle.api.internal.file.collections.FileCollectionAdapter
6 | import org.gradle.api.internal.file.collections.MinimalFileSet
7 |
8 |
9 | class XmlPatchSpec {
10 | File patch;
11 | XmlPatchOptions options;
12 |
13 | AssembledPatch assembled;
14 |
15 | XmlPatchSpec(Map options = [:], File patch) {
16 | this.patch = patch
17 | this.options = new XmlPatchOptions(options)
18 | }
19 |
20 | XmlPatchSpec(Map options = [:], String patch) {
21 | this(options, new File(patch))
22 | }
23 |
24 | AssembledPatch resolve() {
25 | if (assembled == null) {
26 | assembled = new AssembledPatch(patch)
27 | }
28 | return assembled
29 | }
30 |
31 | FileCollection getPatchFragments() {
32 | return new FileCollectionAdapter(new MinimalFileSet() {
33 | @Override
34 | public Set getFiles() {
35 | return resolve().getPatchFiles();
36 | }
37 |
38 | @Override
39 | public String getDisplayName() {
40 | return "Patch fragments referenced by ${patch.absolutePath}"
41 | }
42 | });
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return "PatchSpec{" +
48 | "patch=" + patch +
49 | ", options=" + options +
50 | '}';
51 | }
52 |
53 | void done() {
54 | Set missingPaths = assembled.sourcePaths.toSet()
55 | missingPaths.removeAll(assembled.accessedPaths)
56 | if (!missingPaths.isEmpty()) {
57 | def message = "Some files targeted by XML patch ${patch.absolutePath} were not filtered. " +
58 | "Missing target paths: ${missingPaths}"
59 | throw new FileNotFoundException(message)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/BatchPatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch;
18 |
19 | import static com.github.dnault.xmlpatch.internal.XmlHelper.getChildren;
20 | import static java.util.Arrays.asList;
21 |
22 | import java.io.ByteArrayInputStream;
23 | import java.io.File;
24 | import java.io.FileInputStream;
25 | import java.io.FileNotFoundException;
26 | import java.io.FileOutputStream;
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.OutputStream;
30 | import java.util.ArrayList;
31 | import java.util.HashSet;
32 | import java.util.List;
33 | import java.util.Set;
34 |
35 | import com.github.dnault.xmlpatch.batch.PatchAssembler;
36 | import com.github.dnault.xmlpatch.internal.IoHelper;
37 | import com.github.dnault.xmlpatch.internal.Log;
38 | import com.github.dnault.xmlpatch.internal.XmlHelper;
39 | import joptsimple.OptionException;
40 | import joptsimple.OptionParser;
41 | import joptsimple.OptionSet;
42 | import joptsimple.OptionSpec;
43 | import org.jdom2.Document;
44 | import org.jdom2.Element;
45 | import org.jdom2.output.Format;
46 | import org.jdom2.output.XMLOutputter;
47 |
48 | public class BatchPatcher {
49 |
50 | private static OptionSet parseOptions(OptionParser parser, String... args) throws IOException {
51 | try {
52 | return parser.parse(args);
53 | } catch (OptionException e) {
54 | System.err.println(e.getMessage());
55 | System.err.println();
56 | parser.printHelpOn(System.err);
57 | System.exit(1);
58 | throw new Error();
59 | }
60 | }
61 |
62 | public static void main(String... args) throws Exception {
63 | patch(args);
64 |
65 |
66 | //patch("--patch", "/tmp/patch.xml", "--srcdir", "/tmp/srcdir", "--destdir", "/tmp/patchdest");
67 | }
68 |
69 | public static void patch(String... args) throws Exception {
70 | OptionParser parser = new OptionParser();
71 |
72 | OptionSpec helpOption = parser.acceptsAll(asList("help", "?"), "display this help message");
73 |
74 | OptionSpec patchOption = parser.accepts("patch", "patch file to apply").withRequiredArg().ofType(File.class).required();
75 |
76 | OptionSpec srcdirOption = parser.accepts("srcdir", "for multi-file patches, specifies the base dir for the files to be patched")
77 | .withRequiredArg().ofType(File.class).required();
78 |
79 | OptionSpec destdirOption = parser.accepts("destdir", "for multi-file patches, specifies the output directory. (default: apply the patch in-place)")
80 | .withRequiredArg().ofType(File.class);
81 |
82 | OptionSet options = parseOptions(parser, args);
83 |
84 | if (options.has(helpOption)) {
85 | parser.printHelpOn(System.out);
86 | System.exit(0);
87 | }
88 |
89 | File patchFile = patchOption.value(options);
90 | assertFileExists(patchFile, "patch");
91 |
92 | File srcdir = srcdirOption.value(options);
93 | assertDirectoryExists(srcdir, "srcdir");
94 |
95 | File destdir = options.has(destdirOption) ? destdirOption.value(options) : srcdir;
96 | if (destdir.exists() && !destdir.isDirectory()) {
97 | throw new IllegalArgumentException("destdir is not a directory: " + destdir.getAbsolutePath());
98 | }
99 |
100 | File tempdir = IoHelper.createTempDir();
101 |
102 | try {
103 | Set relativePathsOfPatchedFiles = patch(
104 | assemblePatchDocument(patchFile), srcdir, destdir, tempdir);
105 |
106 | Log.info("writing patched files to destdir: " + destdir.getAbsolutePath());
107 | for (String path : relativePathsOfPatchedFiles) {
108 | File tempFile = new File(tempdir, path);
109 | File destFile = new File(destdir, path);
110 | IoHelper.makeParentDirectory(destFile);
111 | IoHelper.move(tempFile, destFile);
112 | }
113 |
114 | IoHelper.deleteDirectory(tempdir);
115 |
116 | } catch (PatchException e) {
117 | Log.error(e.getMessage());
118 | System.exit(1);
119 | }
120 | }
121 |
122 | public static void assertFileExists(File f, String argname) throws FileNotFoundException {
123 | if (!f.exists()) {
124 | throw new FileNotFoundException(argname + " file not found: " + f.getAbsolutePath());
125 | }
126 | if (!f.isFile()) {
127 | throw new IllegalArgumentException(argname + " is not a file: " + f.getAbsolutePath());
128 | }
129 | }
130 |
131 | public static void assertDirectoryExists(File f, String argname) throws FileNotFoundException {
132 | if (!f.exists()) {
133 | throw new FileNotFoundException(argname + " directory not found: " + f.getAbsolutePath());
134 | }
135 | if (!f.isDirectory()) {
136 | throw new IllegalArgumentException(argname + " is not a directory: " + f.getAbsolutePath());
137 | }
138 | }
139 |
140 | private static List assemblePatchDocument(File patchFile) throws Exception {
141 | return new PatchAssembler().assemble(patchFile).getDiffs();
142 | }
143 |
144 | private static Set patch(List diffs, File srcdir, File destdir, File tempdir) throws Exception {
145 |
146 | Set outputFilePaths = new HashSet<>();
147 |
148 | for (Element diff : diffs) {
149 |
150 | if (diff.getAttribute("file") == null) {
151 | throw new IllegalArgumentException("diff element missing 'file' attribute");
152 | }
153 |
154 | String srcfilePath = diff.getAttributeValue("file");
155 |
156 | File fileToPatch = new File(srcfilePath);
157 | if (fileToPatch.isAbsolute()) {
158 | throw new IllegalArgumentException("not a relative path: " + srcfilePath);
159 | }
160 |
161 | outputFilePaths.add(srcfilePath);
162 |
163 | File alreadyInTempDir = new File(tempdir, srcfilePath);
164 | fileToPatch = alreadyInTempDir.exists() ? alreadyInTempDir : new File(srcdir, srcfilePath);
165 |
166 | Log.info("patching " + srcfilePath + " [from " + fileToPatch.getAbsolutePath() + "]");
167 |
168 | diff.removeAttribute("file");
169 | Format format = Format.getRawFormat();
170 | format.setOmitDeclaration(true);
171 | XMLOutputter outputter = new XMLOutputter(format);
172 | String s = outputter.outputString(diff);
173 |
174 | InputStream diffStream = new ByteArrayInputStream(s.getBytes("UTF-8"));
175 | InputStream inputStream = new FileInputStream(fileToPatch);
176 |
177 | File outputFile = File.createTempFile("xmlpatch", ".xml");
178 | OutputStream outputStream = new FileOutputStream(outputFile);
179 |
180 | Patcher.patch(inputStream, diffStream, outputStream);
181 |
182 | inputStream.close();
183 | outputStream.close();
184 |
185 | File outputTemp = new File(tempdir, srcfilePath);
186 | File tempParentDir = outputTemp.getParentFile();
187 |
188 | if (!tempParentDir.exists() && !tempParentDir.mkdirs()) {
189 | throw new IOException("failed to create directory: " + tempParentDir.getAbsolutePath());
190 | }
191 |
192 | IoHelper.move(outputFile, outputTemp);
193 | }
194 | return outputFilePaths;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/CommandLineDriver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch;
18 |
19 | import java.io.InputStream;
20 | import java.io.FileInputStream;
21 | import java.io.OutputStream;
22 | import java.io.FileOutputStream;
23 | import java.io.FileNotFoundException;
24 | import java.io.IOException;
25 | import java.io.File;
26 |
27 | public class CommandLineDriver {
28 |
29 | public static void main(String... args) throws Exception {
30 |
31 | if (args.length == 0) {
32 | usage();
33 | System.exit(0);
34 | }
35 |
36 | if (args.length != 3) {
37 | System.err.println("incorrect number of arguments");
38 | usage();
39 | System.exit(1);
40 | }
41 |
42 | String input = args[0];
43 | String patch = args[1];
44 | String output = args[2];
45 |
46 | if (input.equals("-") && patch.equals("-")) {
47 | System.err.println("input and patch may not both come from standard input");
48 | System.exit(1);
49 | }
50 |
51 | boolean patchInPlace = !input.equals("-") && isSameFile(input, output);
52 |
53 | if (patchInPlace) {
54 | output = File.createTempFile("xmlpatch", ".xml").getAbsolutePath();
55 | }
56 |
57 | try {
58 | InputStream inputStream = input.equals("-") ? System.in : new FileInputStream(input);
59 | InputStream patchStream = patch.equals("-") ? System.in : new FileInputStream(patch);
60 | OutputStream outputStream = output.equals("-") ? System.out : new FileOutputStream(output);
61 |
62 | Patcher.patch(inputStream, patchStream, outputStream);
63 |
64 | if (patchInPlace) {
65 | if (!new File(output).renameTo(new File(input))) {
66 | throw new IOException("could not rename temp file to " + input);
67 | }
68 | }
69 | }
70 | catch (FileNotFoundException e) {
71 | System.err.println("ERROR: Could not access file: " + e.getMessage());
72 | System.exit(1);
73 | }
74 | }
75 |
76 | private static boolean isSameFile(String pathA, String pathB) throws IOException {
77 | return new File(pathA).getCanonicalPath().equals(new File(pathB).getCanonicalPath());
78 | }
79 |
80 | private static void usage() {
81 | System.err.println("USAGE: java -jar xml-patch.jar ");
82 | System.err.println(" A dash (-) may be used to indicate standard input / output");
83 | System.err.println(" The patch is an XML diff document as defined by RFC 5261");
84 | System.err.println(" or an XML patch document as defined by RFC 7351");
85 | System.exit(1);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/ErrorCondition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch;
18 |
19 | public enum ErrorCondition {
20 | INVALID_ATTRIBUTE_VALUE,
21 | INVALID_CHARACTER_SET,
22 | INVALID_DIFF_FORMAT,
23 | INVALID_ENTITY_DECLARATION,
24 | INVALID_NAMESPACE_PREFIX,
25 | INVALID_NAMESPACE_URI,
26 | INVALID_NODE_TYPES,
27 | INVALID_PATCH_DIRECTIVE,
28 | INVALID_ROOT_ELEMENT_OPERATION,
29 | INVALID_XML_PROLOG_OPERATION,
30 | INVALID_WHITESPACE_DIRECTIVE,
31 | UNLOCATED_NODE,
32 | UNSUPORTED_ID_FUNCTION,
33 | UNSUPPORTED_XML_ID,
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/PatchException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch;
18 |
19 |
20 | public class PatchException extends RuntimeException {
21 |
22 | private final ErrorCondition errorCondition;
23 |
24 | public PatchException(ErrorCondition errorCondition) {
25 | this.errorCondition = errorCondition;
26 | }
27 |
28 | public PatchException(ErrorCondition errorCondition, String message,
29 | Throwable cause) {
30 | super(message, cause);
31 | this.errorCondition = errorCondition;
32 | }
33 |
34 | public PatchException(ErrorCondition errorCondition, String message) {
35 | super(message);
36 | this.errorCondition = errorCondition;
37 | }
38 |
39 | public PatchException(ErrorCondition errorCondition, Throwable t) {
40 | super(t);
41 | this.errorCondition = errorCondition;
42 | }
43 |
44 | public ErrorCondition getErrorCondition() {
45 | return errorCondition;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/Patcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch;
18 |
19 | import com.github.dnault.xmlpatch.internal.XmlHelper;
20 | import org.jdom2.Attribute;
21 | import org.jdom2.Namespace;
22 | import org.jdom2.Text;
23 | import org.jdom2.Comment;
24 | import org.jdom2.ProcessingInstruction;
25 | import org.jdom2.Content;
26 | import org.jdom2.Element;
27 | import org.jdom2.Document;
28 | import org.jdom2.Parent;
29 | import org.jdom2.IllegalAddException;
30 | import org.jdom2.IllegalNameException;
31 | import org.jdom2.JDOMException;
32 | import org.jdom2.output.Format;
33 | import org.jdom2.output.XMLOutputter;
34 | import org.jdom2.xpath.XPath;
35 |
36 | import java.io.ByteArrayInputStream;
37 | import java.io.IOException;
38 | import java.io.InputStream;
39 | import java.io.OutputStream;
40 |
41 | import java.util.List;
42 | import java.util.Map;
43 | import java.util.Collections;
44 | import java.util.ArrayList;
45 | import java.util.Arrays;
46 | import java.util.Iterator;
47 |
48 | import static com.github.dnault.xmlpatch.internal.XmlHelper.getInScopeNamespaceDeclarations;
49 |
50 | @SuppressWarnings("unchecked")
51 | public class Patcher {
52 |
53 | static private boolean lenient = true;
54 | static private boolean trimMultilineText = true;
55 |
56 | public static void patch(InputStream target, InputStream diff,
57 | OutputStream result) throws IOException {
58 |
59 | try {
60 | Document targetDoc = XmlHelper.parse(target);
61 | Document diffDoc = XmlHelper.parse(diff);
62 |
63 | Element diffRoot = diffDoc.getRootElement();
64 | for (Object o : diffRoot.getChildren()) {
65 | patch(targetDoc, (Element) o);
66 | }
67 |
68 | XMLOutputter outputter = new XMLOutputter();
69 |
70 | // Use the separator that is appropriate for the platform.
71 | Format format = Format.getRawFormat();
72 | format.setLineSeparator(System.getProperty("line.separator"));
73 | outputter.setFormat(format);
74 |
75 | outputter.output(targetDoc, result);
76 | } catch (JDOMException e) {
77 | throw new PatchException(ErrorCondition.INVALID_DIFF_FORMAT, e);
78 | }
79 | }
80 |
81 | private static void patch(Document target, Element patch) throws JDOMException {
82 | String operation = patch.getName();
83 | switch (operation) {
84 | case "add":
85 | add(target, patch);
86 | break;
87 | case "replace":
88 | replace(target, patch);
89 | break;
90 | case "remove":
91 | remove(target, patch);
92 | break;
93 | default:
94 | throw new RuntimeException("unknown operation: " + operation);
95 | }
96 | }
97 |
98 | private static void throwIfNotSingleNodeOfType(List content, Class expectedClass) {
99 | if (content.size() != 1 || !content.get(0).getClass().equals(expectedClass)) {
100 | throw new PatchException(ErrorCondition.INVALID_NODE_TYPES, "expected replacement to be a single content node of type "
101 | + expectedClass.getSimpleName());
102 | }
103 | }
104 |
105 | private static void replace(Document target, Element patch) throws JDOMException {
106 | for (Object node : selectNodes(target, patch)) {
107 | doReplace(patch, node);
108 | }
109 | }
110 |
111 | public static void main(String[] args) throws Exception {
112 | Document doc = XmlHelper.parse(new ByteArrayInputStream(" ".getBytes("UTF-8")));
113 | Element patch = doc.getRootElement();
114 |
115 | Document d = new Document();
116 | Element e = new Element("foo").setAttribute("message", "goodbye").setText("asdasda");
117 | d.addContent(e);
118 |
119 | doReplace(patch, e.getContent(0));
120 | System.out.println("[" + e.getText() + "]");
121 | }
122 |
123 | private static String getTextMaybeTrim(Element patch) {
124 | // Implement a non-standard "trim" attribute on patch nodes.
125 | String value = patch.getText();
126 | String override = patch.getAttributeValue("trim");
127 | if (override != null) {
128 | if (override.equals("true")) {
129 | return value.trim();
130 | }
131 | if (override.equals("false")) {
132 | return value;
133 | }
134 | throw new RuntimeException("expected 'trim' attribute to be 'true' or 'false' but found " + override);
135 | }
136 |
137 | return trimMultilineText && isMultiline(value) ? value.trim() : value;
138 | }
139 |
140 | private static void doReplace(Element patch, Object node) throws JDOMException {
141 |
142 | if (node instanceof Attribute) {
143 | for (Object o : patch.getContent()) {
144 | if (!(o instanceof Text)) {
145 | throw new PatchException(ErrorCondition.INVALID_NODE_TYPES, "attribute value replacement must be text");
146 | }
147 | }
148 |
149 | ((Attribute) node).setValue(getTextMaybeTrim(patch));
150 | return;
151 | }
152 |
153 | if (node instanceof Element || node instanceof Comment || node instanceof ProcessingInstruction) {
154 | List replacement = patch.cloneContent();
155 |
156 | if (lenient) {
157 | // ignore whitespace siblings so the diff document can be pretty
158 | for (Iterator i = replacement.iterator(); i.hasNext(); ) {
159 | if (isWhitespace(i.next())) {
160 | i.remove();
161 | }
162 | }
163 | }
164 |
165 | throwIfNotSingleNodeOfType(replacement, node.getClass());
166 |
167 | Content replaceMe = (Content) node;
168 | Parent p = replaceMe.getParent();
169 | int index = XmlHelper.indexOf(p, replaceMe);
170 | replaceMe.detach();
171 |
172 | if (p instanceof Element) {
173 | canonicalizeNamespaces((Element) p, replacement);
174 | ((Element) p).addContent(index, replacement);
175 |
176 | } else if (p instanceof Document) {
177 | if (!(node instanceof Element)) {
178 | throw new PatchException(ErrorCondition.INVALID_XML_PROLOG_OPERATION,
179 | "can't replace prolog nodes");
180 | }
181 |
182 | ((Document) p).setRootElement((Element) replacement.get(0));
183 |
184 | } else {
185 | // shouldn't happen.
186 | throw new RuntimeException("expected Parent to be either Document or Element but found " + p.getClass());
187 | }
188 |
189 | return;
190 | }
191 |
192 | if (node instanceof Text) {
193 | List replacement = patch.cloneContent();
194 | if (!replacement.isEmpty()) {
195 | throwIfNotSingleNodeOfType(replacement, Text.class);
196 | }
197 |
198 | Content replaceMe = (Content) node;
199 | Element p = replaceMe.getParentElement();
200 | int index = XmlHelper.indexOf(p, replaceMe);
201 | replaceMe.detach();
202 |
203 | String replacementText = getTextMaybeTrim(patch);
204 | if (replacementText.length() > 0) {
205 | p.addContent(index, new Text(replacementText));
206 | }
207 |
208 | return;
209 | }
210 |
211 |
212 | if (node instanceof Namespace) {
213 | if (true) {
214 | throw new UnsupportedOperationException("removing namespace declarations is not yet implemented");
215 | }
216 |
217 |
218 | // TODO: This needs to be the element in the target that
219 | // contains the namespace declaration
220 | Element parent = new Element("");
221 |
222 | Namespace ns = (Namespace) node;
223 |
224 | String newUri = getTextMaybeTrim(patch);
225 | Namespace newNamespace = createNamespace(ns.getPrefix(), newUri);
226 |
227 | if (parent.getAdditionalNamespaces().contains(ns)) {
228 | parent.removeNamespaceDeclaration(ns);
229 | parent.addNamespaceDeclaration(newNamespace);
230 | } else if (parent.getNamespace().getPrefix().equals(ns.getPrefix())) {
231 | parent.setNamespace(newNamespace);
232 | } else {
233 | throw new PatchException(ErrorCondition.UNLOCATED_NODE);
234 | }
235 | return;
236 | }
237 |
238 | throw new PatchException(ErrorCondition.INVALID_PATCH_DIRECTIVE,
239 | "target node '" + node + "' was not an Attribute, Element, Comment, Text, Processing Instruction, or Namespace");
240 | }
241 |
242 | private static boolean isMultiline(String replacement) {
243 | return replacement.contains("\n") || replacement.contains("\r");
244 | }
245 |
246 | private static boolean isWhitespace(Content node) {
247 | // todo stricter interpretation of whitespace?
248 | return node instanceof Text && node.getValue().trim().length() == 0;
249 | }
250 |
251 | private static void add(Document target, Element patch) throws JDOMException {
252 | for (Object node : selectNodes(target, patch)) {
253 | doAdd(patch, node);
254 | }
255 | }
256 |
257 | private static void doAdd(Element patch, Object nodeObject) throws JDOMException {
258 | String position = patch.getAttributeValue("pos");
259 | String type = patch.getAttributeValue("type");
260 |
261 | Content node = (Content) nodeObject;
262 |
263 | if (type != null) {
264 | if (type.startsWith("@")) {
265 | addAttribute(patch, type.substring(1), asElement(node));
266 | return;
267 | }
268 | if (type.startsWith("namespace::")) {
269 | addNamespaceDeclaration(patch, type.substring(11), asElement(node));
270 | return;
271 | }
272 | // todo validation
273 | }
274 |
275 | if ("before".equals(position) || "after".equals(position)) {
276 | if (node.getParentElement() == null) {
277 | for (Object o : patch.getContent()) {
278 | if (!(o instanceof Comment) && !(o instanceof ProcessingInstruction)) {
279 | throw new PatchException(ErrorCondition.INVALID_ROOT_ELEMENT_OPERATION);
280 | }
281 | }
282 | }
283 | }
284 |
285 | List newContent = patch.cloneContent();
286 | try {
287 | if (position == null) {
288 | // default is "append"
289 | Element e = asElement(node);
290 | canonicalizeNamespaces(e, newContent);
291 | e.getContent().addAll(newContent);
292 | } else if ("prepend".equals(position)) {
293 | Element e = asElement(node);
294 | canonicalizeNamespaces(e, newContent);
295 | e.getContent().addAll(0, newContent);
296 | } else if ("before".equals(position)) {
297 | Parent p = node.getParent();
298 |
299 | if (p instanceof Element) {
300 | canonicalizeNamespaces((Element) p, newContent);
301 | }
302 |
303 | int nodeIndex = XmlHelper.indexOf(p, node);
304 | p.getContent().addAll(nodeIndex, newContent);
305 | } else if ("after".equals(position)) {
306 | Parent p = node.getParent();
307 |
308 | if (p instanceof Element) {
309 | canonicalizeNamespaces((Element) p, newContent);
310 | }
311 |
312 | int nodeIndex = XmlHelper.indexOf(p, node);
313 | p.getContent().addAll(nodeIndex + 1, newContent);
314 | } else {
315 | throw new PatchException(ErrorCondition.INVALID_DIFF_FORMAT,
316 | "unrecognized position '" + position + "' for add; expected one of " + Arrays.toString(new String[]{"before", "after", "prepend"}));
317 | }
318 | } catch (IllegalAddException e) {
319 | // todo nice message
320 | throw new PatchException(ErrorCondition.INVALID_PATCH_DIRECTIVE, e);
321 | }
322 |
323 | }
324 |
325 | private static void addNamespaceDeclaration(Element patch, String prefix, Element target) {
326 |
327 | Namespace ns = createNamespace(prefix, getTextMaybeTrim(patch));
328 | target.addNamespaceDeclaration(ns);
329 | }
330 |
331 | private static Namespace createNamespace(String prefix, String uri) {
332 | try {
333 | return Namespace.getNamespace(prefix, uri);
334 | } catch (IllegalNameException e) {
335 | throw new PatchException(ErrorCondition.INVALID_NAMESPACE_URI, e.getMessage());
336 | }
337 | }
338 |
339 | private static void addAttribute(Element patch, String name, Element target) {
340 |
341 | String prefix = null;
342 | if (name.contains(":")) {
343 | String[] prefixAndName = name.split(":");
344 | // todo validate length = 2?
345 | prefix = prefixAndName[0];
346 | name = prefixAndName[1];
347 | }
348 |
349 | String value = getTextMaybeTrim(patch);
350 | Attribute a = new Attribute(name, value);
351 |
352 | if (prefix != null) {
353 | Namespace ns = patch.getNamespace(prefix);
354 | if (ns == null) {
355 | throw new PatchException(
356 | ErrorCondition.INVALID_NAMESPACE_PREFIX,
357 | "could not resolve namespace prefix '" + prefix
358 | + "' in the context of the diff document");
359 | }
360 | a.setNamespace(ns);
361 | }
362 | canonicalizeNamespace(a, getInScopeNamespaceDeclarations(target));
363 | target.setAttribute(a);
364 | }
365 |
366 | private static void canonicalizeNamespaces(Element scope,
367 | List content) {
368 |
369 | Map prefixToUri = XmlHelper
370 | .getInScopeNamespaceDeclarations(scope);
371 |
372 | for (Content c : content) {
373 | if (c instanceof Element) {
374 | canonicalizeNamespace((Element) c, prefixToUri);
375 | }
376 | }
377 |
378 | }
379 |
380 | private static void canonicalizeNamespace(Attribute a,
381 | Map prefixToUri) {
382 | for (Map.Entry entry : prefixToUri.entrySet()) {
383 | if (a.getNamespaceURI().equals(entry.getValue())) {
384 | if (entry.getKey().equals("")) {
385 | // default namespace doesn't apply to attributes
386 | continue;
387 | }
388 | a.setNamespace(Namespace.getNamespace(entry.getKey(), entry
389 | .getValue()));
390 | break;
391 |
392 | }
393 | }
394 | }
395 |
396 | private static void canonicalizeNamespace(Element e,
397 | Map prefixToUri) {
398 | Namespace ns = e.getNamespace();
399 |
400 | for (Map.Entry entry : prefixToUri.entrySet()) {
401 | if (ns.getURI().equals(entry.getValue())) {
402 | e.setNamespace(Namespace.getNamespace(entry.getKey(), entry
403 | .getValue()));
404 | break;
405 | }
406 | }
407 |
408 | for (Object o : e.getAttributes()) {
409 | canonicalizeNamespace((Attribute) o, prefixToUri);
410 | }
411 |
412 | for (Object o : e.getChildren()) {
413 | canonicalizeNamespace((Element) o, prefixToUri);
414 | }
415 | }
416 |
417 | private static Element asElement(Content node) {
418 | try {
419 | return (Element) node;
420 | } catch (ClassCastException e) {
421 | throw new PatchException(ErrorCondition.INVALID_PATCH_DIRECTIVE,
422 | "selected node is not an element");
423 | }
424 | }
425 |
426 | private static List selectNodes(Document target, Element patch) throws JDOMException {
427 |
428 | boolean isMultiSelect = false;
429 |
430 | String selector = patch.getAttributeValue("sel");
431 | if (selector == null) {
432 | selector = patch.getAttributeValue("msel");
433 | isMultiSelect = true;
434 | }
435 |
436 | XPath xpath = XPath.newInstance(selector);
437 | bindNamespacePrefixes(xpath, patch);
438 | List content = xpath.selectNodes(target);
439 |
440 | if (content.isEmpty()) {
441 | throw new PatchException(ErrorCondition.UNLOCATED_NODE,
442 | "no matches for selector \"" + selector + "\"");
443 | }
444 | if (!isMultiSelect && content.size() > 1) {
445 | throw new PatchException(ErrorCondition.UNLOCATED_NODE,
446 | "more that one match for selector \"" + selector + "\" -- if you want to select multiple nodes, use the 'msel' attribute instead of 'sel'.");
447 | }
448 | return content;
449 | }
450 |
451 | private static void bindNamespacePrefixes(XPath xpath, Element patch) {
452 | List chain = new ArrayList<>();
453 | for (Element e = patch; e != null; e = e.getParentElement()) {
454 | chain.add(e);
455 | }
456 |
457 | // namespace definitions on the child should override any parent defintions with the same prefix
458 | Collections.reverse(chain);
459 |
460 | for (Element e : chain) {
461 | for (Object o : e.getAdditionalNamespaces()) {
462 | xpath.addNamespace((Namespace) o);
463 | }
464 | }
465 | }
466 |
467 | private static void remove(Document target, Element patch) throws JDOMException {
468 | for (Object node : selectNodes(target, patch)) {
469 | doRemove(patch, node);
470 | }
471 | }
472 |
473 | private static void doRemove(Element patch, Object node) throws JDOMException {
474 |
475 | if (node instanceof Element || node instanceof Comment || node instanceof ProcessingInstruction) {
476 |
477 | String ws = patch.getAttributeValue("ws");
478 | boolean before = "both".equals(ws) || "before".equals(ws);
479 | boolean after = "both".equals(ws) || "after".equals(ws);
480 |
481 | Content c = (Content) node;
482 | Element e = c.getParentElement();
483 | if (e == null) {
484 | throw new PatchException(ErrorCondition.INVALID_ROOT_ELEMENT_OPERATION,
485 | "can't remove root element");
486 | }
487 |
488 | int index = e.indexOf(c);
489 | List nodesToDetach = new ArrayList<>();
490 | nodesToDetach.add(c);
491 |
492 | if (before) {
493 | nodesToDetach.add(getWhitespace(e, index - 1));
494 | }
495 | if (after) {
496 | nodesToDetach.add(getWhitespace(e, index + 1));
497 | }
498 |
499 | for (Content detachMe : nodesToDetach) {
500 | detachMe.detach();
501 | }
502 |
503 | return;
504 | }
505 |
506 | if (patch.getAttribute("ws") != null) {
507 | throw new PatchException(ErrorCondition.INVALID_PATCH_DIRECTIVE,
508 | "The 'ws' attribute is not allowed when removing " +
509 | "Attribute, Text or Namespace nodes.");
510 | }
511 |
512 | if (node instanceof Attribute) {
513 | Attribute a = (Attribute) node;
514 | a.getParent().removeAttribute(a);
515 | return;
516 | }
517 |
518 | if (node instanceof Text) {
519 | ((Content) node).detach();
520 | return;
521 | }
522 |
523 | if (node instanceof Namespace) {
524 | throw new UnsupportedOperationException("removing namespace declarations is not yet implemented");
525 | // return;
526 | }
527 | }
528 |
529 | private static Text getWhitespace(Element parent, int i) {
530 |
531 | try {
532 | Content c = parent.getContent(i);
533 | if (isWhitespace(c)) {
534 | return (Text) c;
535 | }
536 | } catch (IndexOutOfBoundsException noSuchSibling) {
537 | // invalid whitepace directive
538 | }
539 |
540 | throw new PatchException(ErrorCondition.INVALID_WHITESPACE_DIRECTIVE,
541 | "sibling is not a whitespace node");
542 | }
543 |
544 | }
545 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/ant/AbstractPatchTask.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.ant;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.OutputStream;
6 | import java.nio.charset.StandardCharsets;
7 |
8 | import org.apache.tools.ant.BuildException;
9 | import org.apache.tools.ant.Task;
10 |
11 | public abstract class AbstractPatchTask extends Task {
12 | private String src;
13 | private String dest;
14 | private String patch;
15 | private String inlinePatch;
16 |
17 | public String getSrc() {
18 | return src;
19 | }
20 |
21 | public void setSrc(String src) {
22 | this.src = new File(src).getAbsolutePath();
23 | }
24 |
25 | public String getDest() {
26 | return dest;
27 | }
28 |
29 | public void setDest(String dest) {
30 | this.dest = new File(dest).getAbsolutePath();
31 | }
32 |
33 | public String getPatch() {
34 | return patch;
35 | }
36 |
37 | public void setPatch(String patch) {
38 | this.patch = patch;
39 | }
40 |
41 | public String getInlinePatch() {
42 | return inlinePatch;
43 | }
44 |
45 | public void addText(String text) {
46 | this.inlinePatch = getProject().replaceProperties(text);
47 | }
48 |
49 | protected void checkArgs() {
50 | if (src == null) {
51 | throw new BuildException("missing 'src' attribute");
52 | }
53 |
54 | if (patch == null && inlinePatch == null) {
55 | throw new BuildException("missing 'patch' attribute or nested text");
56 | }
57 |
58 | if (patch != null && inlinePatch != null) {
59 | throw new BuildException("must not provide both 'patch' attribute and nested text");
60 | }
61 |
62 | if (dest == null) {
63 | dest = src;
64 | }
65 | }
66 |
67 | public void execute() {
68 | checkArgs();
69 |
70 | File tempFile = null;
71 | try {
72 | if (getInlinePatch() != null) {
73 | try {
74 | tempFile = File.createTempFile("inline-patch-", ".xml");
75 | setPatch(tempFile.getAbsolutePath());
76 |
77 | try (OutputStream os = new FileOutputStream(tempFile)) {
78 | os.write(getInlinePatch().getBytes(StandardCharsets.UTF_8));
79 | }
80 | } catch (Exception e) {
81 | throw new BuildException(e);
82 | }
83 | }
84 |
85 | try {
86 | log("Applying XML patch");
87 | log(" src: " + getSrc());
88 | log(" patch: " + getPatch());
89 | log(" dest: " + getDest());
90 |
91 | doPatch();
92 |
93 | } catch (Exception e) {
94 | throw new BuildException(e);
95 | }
96 | } finally {
97 | if (tempFile != null) {
98 | if (!tempFile.delete()) {
99 | log("Failed to delete temp file for inline XML patch: " + tempFile.getAbsolutePath());
100 | }
101 | }
102 | }
103 | }
104 |
105 | protected abstract void doPatch() throws Exception;
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/ant/AntXmlPatchFilter.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.ant;
2 |
3 | import java.io.Reader;
4 | import java.io.StringReader;
5 |
6 | import com.github.dnault.xmlpatch.filter.XmlPatch;
7 | import org.apache.tools.ant.filters.BaseFilterReader;
8 | import org.apache.tools.ant.filters.ChainableReader;
9 | import org.apache.tools.ant.types.Parameter;
10 | import org.apache.tools.ant.types.Parameterizable;
11 |
12 | public class AntXmlPatchFilter extends XmlPatch implements ChainableReader, Parameterizable {
13 | /**
14 | * Constructor for "dummy" instances.
15 | *
16 | * @see BaseFilterReader#BaseFilterReader()
17 | */
18 | @SuppressWarnings("unused")
19 | public AntXmlPatchFilter() {
20 | super(new StringReader(""));
21 | org.apache.tools.ant.util.FileUtils.close(this);
22 | }
23 |
24 | public AntXmlPatchFilter(Reader in) {
25 | super(in);
26 | }
27 |
28 | @Override
29 | public Reader chain(Reader reader) {
30 | AntXmlPatchFilter r = new AntXmlPatchFilter(reader);
31 | r.setPatch(getPatch());
32 | return r;
33 | }
34 |
35 | @Override
36 | public void setParameters(Parameter[] parameters) {
37 | if (parameters != null) {
38 | for (Parameter p : parameters) {
39 | if (p.getName().equalsIgnoreCase("patch")) {
40 | setPatch(p.getValue());
41 | } else {
42 | throw new RuntimeException("unrecognized parameter: " + p.getName());
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/ant/PatchXmlDir.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.ant;
2 |
3 | import com.github.dnault.xmlpatch.BatchPatcher;
4 |
5 | public class PatchXmlDir extends AbstractPatchTask {
6 | @Override
7 | protected void doPatch() throws Exception {
8 | BatchPatcher.patch("--patch=" + getPatch(), "--srcdir=" + getSrc(), "--destdir=" + getDest());
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/ant/PatchXmlFile.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.ant;
2 |
3 | import com.github.dnault.xmlpatch.CommandLineDriver;
4 |
5 | public class PatchXmlFile extends AbstractPatchTask {
6 | @Override
7 | protected void doPatch() throws Exception {
8 | CommandLineDriver.main(getSrc(), getPatch(), getDest());
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/batch/AssembledPatch.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.batch;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import java.io.File;
6 | import java.util.ArrayList;
7 | import java.util.HashSet;
8 | import java.util.LinkedHashSet;
9 | import java.util.List;
10 | import java.util.Set;
11 |
12 | import org.jdom2.Element;
13 |
14 | public class AssembledPatch {
15 | private LinkedHashSet patchFiles = new LinkedHashSet<>();
16 | private List diffs = new ArrayList<>();
17 | private Set accessedPaths = new HashSet<>();
18 |
19 | public AssembledPatch() {
20 | }
21 |
22 | public AssembledPatch(File patch) throws Exception {
23 | new PatchAssembler().assembleRecursive(patch, this);
24 | }
25 |
26 | /**
27 | * @return the set of files that comprise the patch
28 | */
29 | public LinkedHashSet getPatchFiles() {
30 | return patchFiles;
31 | }
32 |
33 | public boolean addPatchFile(File includedFile) {
34 | return this.patchFiles.add(includedFile);
35 | }
36 |
37 | public List getDiffs() {
38 | return diffs;
39 | }
40 |
41 | public List getDiffs(String sourcePath) {
42 | requireNonNull(sourcePath);
43 |
44 | List matchingDiffs = new ArrayList<>(0);
45 | for (Element e : diffs) {
46 | if (sourcePath.equals(e.getAttributeValue("file"))) {
47 | matchingDiffs.add(e);
48 | accessedPaths.add(sourcePath);
49 | }
50 | }
51 | return matchingDiffs;
52 | }
53 |
54 | public Set getSourcePaths() {
55 | Set sourcePaths = new HashSet<>();
56 | for (Element e : diffs) {
57 | sourcePaths.add(e.getAttributeValue("file"));
58 | }
59 | return sourcePaths;
60 | }
61 |
62 | public Set getAccessedPaths() {
63 | return accessedPaths;
64 | }
65 |
66 | public void addDif(Element diff) {
67 | this.diffs.add(diff);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/batch/PatchAssembler.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.batch;
2 |
3 | import static com.github.dnault.xmlpatch.internal.XmlHelper.getChildren;
4 |
5 | import java.io.File;
6 |
7 | import com.github.dnault.xmlpatch.internal.Log;
8 | import com.github.dnault.xmlpatch.internal.XmlHelper;
9 | import org.jdom2.Document;
10 | import org.jdom2.Element;
11 |
12 | public class PatchAssembler {
13 |
14 | public AssembledPatch assemble(File patchFile) throws Exception {
15 | AssembledPatch assembled = new AssembledPatch();
16 | assembleRecursive(patchFile, assembled);
17 | return assembled;
18 | }
19 |
20 | protected void assembleRecursive(File patchFile, AssembledPatch assembled) throws Exception {
21 |
22 | String path = patchFile.getAbsolutePath();
23 | if (!assembled.addPatchFile(patchFile)) {
24 | // already included this file.
25 | return;
26 | }
27 |
28 | Log.info("Reading XML patch file: " + path);
29 |
30 | Document doc = XmlHelper.parse(patchFile);
31 | Element batchElement = doc.getRootElement();
32 |
33 | if (!batchElement.getName().equals("diffs")) {
34 | throw new IllegalArgumentException(path + ": expected root element of patch document to be 'diffs' but found '" + batchElement.getName() + "'");
35 | }
36 |
37 | for (Element diff : getChildren(batchElement)) {
38 | if (diff.getName().equals("include")) {
39 | String includeFilename = diff.getAttributeValue("file");
40 | if (includeFilename == null) {
41 | throw new IllegalArgumentException(path + ": element 'include' missing 'file' attribute");
42 | }
43 |
44 | File includeFile = new File(patchFile.getParent(), includeFilename);
45 | assembleRecursive(includeFile, assembled);
46 | continue;
47 | }
48 |
49 | if (!diff.getName().equals("diff")) {
50 | throw new IllegalArgumentException(path + ": unexpected element '" + diff.getName() + "' in patch document, expected 'diff' or 'include'");
51 | }
52 |
53 | if (diff.getAttribute("file") == null) {
54 | throw new IllegalArgumentException(path + ": 'diff' element missing 'file' attribute");
55 | }
56 |
57 | assembled.addDif((Element) diff.clone());
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/filter/XmlPatch.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.filter;
2 |
3 | import static java.nio.charset.StandardCharsets.UTF_8;
4 |
5 | import java.io.File;
6 | import java.io.FileOutputStream;
7 | import java.io.FileReader;
8 | import java.io.IOException;
9 | import java.io.OutputStreamWriter;
10 | import java.io.Reader;
11 | import java.io.Writer;
12 |
13 | import com.github.dnault.xmlpatch.CommandLineDriver;
14 | import com.github.dnault.xmlpatch.internal.DeferredInitFilterReader;
15 | import org.apache.commons.io.FileUtils;
16 | import org.apache.commons.io.IOUtils;
17 |
18 | public class XmlPatch extends DeferredInitFilterReader {
19 | private String patch;
20 | private File output;
21 |
22 | public XmlPatch(Reader in) {
23 | super(in);
24 | }
25 |
26 | public void setPatch(String patch) {
27 | if (patch != null) {
28 | patch = new File(patch).getAbsolutePath();
29 | }
30 | this.patch = patch;
31 | }
32 |
33 | public String getPatch() {
34 | return patch;
35 | }
36 |
37 | @Override
38 | protected void initialize() {
39 | if (patch == null) {
40 | throw new RuntimeException("missing 'patch' parameter, path to patch file");
41 | }
42 |
43 | try {
44 | File input = File.createTempFile("xml-patch-input-", ".xml");
45 | try {
46 | output = File.createTempFile("xml-patch-result-", ".xml");
47 |
48 | try (FileOutputStream os = new FileOutputStream(input);
49 | Writer writer = new OutputStreamWriter(os, UTF_8);
50 | Reader reader = in) {
51 | IOUtils.copy(reader, writer);
52 | }
53 |
54 | CommandLineDriver.main(input.getAbsolutePath(), patch, output.getAbsolutePath());
55 | in = new FileReader(output);
56 |
57 | } finally {
58 | FileUtils.deleteQuietly(input);
59 | }
60 |
61 | } catch (Exception e) {
62 | throw new RuntimeException(e);
63 | }
64 | }
65 |
66 | @Override
67 | public void close() throws IOException {
68 | try {
69 | super.close();
70 | } finally {
71 | FileUtils.deleteQuietly(output);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/filter/multi/XmlPatchOptions.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.filter.multi;
2 |
3 | public class XmlPatchOptions {
4 | private boolean failOnMissingPatch = true;
5 | private boolean failOnMissingSourcePath = true;
6 |
7 | public boolean isFailOnMissingPatch() {
8 | return failOnMissingPatch;
9 | }
10 |
11 | public void setFailOnMissingPatch(boolean failOnMissingPatch) {
12 | this.failOnMissingPatch = failOnMissingPatch;
13 | }
14 |
15 | public boolean isFailOnMissingSourcePath() {
16 | return failOnMissingSourcePath;
17 | }
18 |
19 | public void setFailOnMissingSourcePath(boolean failOnMissingSourcePath) {
20 | this.failOnMissingSourcePath = failOnMissingSourcePath;
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "XmlPatchOptions{" +
26 | "failOnMissingPatch=" + failOnMissingPatch +
27 | ", failOnMissingSourcePath=" + failOnMissingSourcePath +
28 | '}';
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/DeferredInitFilterReader.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.internal;
2 |
3 | import java.io.FilterReader;
4 | import java.io.IOException;
5 | import java.io.Reader;
6 |
7 | public abstract class DeferredInitFilterReader extends FilterReader {
8 | private boolean initialized;
9 |
10 | protected DeferredInitFilterReader(Reader in) {
11 | super(in);
12 | }
13 |
14 | abstract protected void initialize();
15 |
16 | private void initializeIfNecessary() {
17 | if (!initialized) {
18 | initialize();
19 | initialized = true;
20 | }
21 | }
22 |
23 | @Override
24 | public int read() throws IOException {
25 | initializeIfNecessary();
26 | return super.read();
27 | }
28 |
29 | @Override
30 | public int read(char cbuf[], int off, int len) throws IOException {
31 | initializeIfNecessary();
32 | return super.read(cbuf, off, len);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/IoHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.internal;
18 |
19 | import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
20 |
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.nio.file.FileVisitResult;
24 | import java.nio.file.Files;
25 | import java.nio.file.Path;
26 | import java.nio.file.SimpleFileVisitor;
27 | import java.nio.file.attribute.BasicFileAttributes;
28 |
29 | public class IoHelper {
30 | public static void move(File from, File to) throws IOException {
31 | Files.move(from.toPath(), to.toPath(), REPLACE_EXISTING);
32 | }
33 |
34 | public static File createTempDir() throws IOException {
35 | return Files.createTempDirectory("xmlpatch-").toFile();
36 | }
37 |
38 | public static void makeParentDirectory(File file) throws IOException {
39 | File parent = file.getParentFile();
40 | if (!parent.exists()) {
41 | if (!parent.mkdirs()) {
42 | throw new IOException("failed to make directory: " + parent.getAbsolutePath());
43 | }
44 | } else if (!parent.isDirectory()) {
45 | throw new IOException("not a directory: " + parent.getAbsolutePath());
46 | }
47 | }
48 |
49 | public static void deleteDirectory(File dir) throws IOException {
50 | Files.walkFileTree(dir.toPath(), new SimpleFileVisitor() {
51 | @Override
52 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
53 | Files.delete(file);
54 | return FileVisitResult.CONTINUE;
55 | }
56 |
57 | @Override
58 | public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
59 | // try to delete the file anyway, even if its attributes could not be read,
60 | // since delete-only access is theoretically possible
61 | Files.delete(file);
62 | return FileVisitResult.CONTINUE;
63 | }
64 |
65 | @Override
66 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
67 | if (exc == null) {
68 | Files.delete(dir);
69 | return FileVisitResult.CONTINUE;
70 | } else {
71 | // directory iteration failed; propagate exception
72 | throw exc;
73 | }
74 | }
75 | });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/Log.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.internal;
2 |
3 | import com.github.dnault.xmlpatch.internal.logging.ConsoleLogger;
4 | import com.github.dnault.xmlpatch.internal.logging.Slf4jLogger;
5 | import com.github.dnault.xmlpatch.internal.logging.XmlPatchLogger;
6 |
7 | public class Log {
8 | private static final XmlPatchLogger logger = isSlf4jPresent() ? new Slf4jLogger() : new ConsoleLogger();
9 |
10 | private static boolean isSlf4jPresent() {
11 | try {
12 | Class.forName("org.slf4j.Logger");
13 | return true;
14 | } catch (ClassNotFoundException e) {
15 | return false;
16 | }
17 | }
18 |
19 | public static void debug(String s) {
20 | logger.debug(s);
21 | }
22 |
23 | public static void info(String s) {
24 | logger.info(s);
25 | }
26 |
27 | public static void warn(String s) {
28 | logger.warn(s);
29 | }
30 |
31 | public static void error(String s) {
32 | logger.error(s);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/XmlHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.internal;
18 |
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.FileNotFoundException;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.util.ArrayList;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 |
29 | import org.jdom2.Content;
30 | import org.jdom2.Document;
31 | import org.jdom2.Element;
32 | import org.jdom2.JDOMException;
33 | import org.jdom2.Namespace;
34 | import org.jdom2.Parent;
35 | import org.jdom2.input.SAXBuilder;
36 |
37 | public class XmlHelper {
38 |
39 | public static Document parse(File f) throws IOException, JDOMException {
40 | try (FileInputStream fis = new FileInputStream(f)) {
41 | return XmlHelper.parse(fis);
42 | }
43 | }
44 |
45 | public static Document parse(InputStream is) throws IOException, JDOMException {
46 | SAXBuilder builder = new SAXBuilder();
47 |
48 | // DTD validation is makes an HTTP request and is slow. Don't need this feature, so disable it.
49 | builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
50 |
51 | Document doc = builder.build(is);
52 | return doc;
53 | }
54 |
55 | public static List clone(List content) {
56 | List cloned = new ArrayList();
57 | for (Object o : content) {
58 | cloned.add((Content) ((Content)o).clone());
59 | }
60 | return cloned;
61 | }
62 |
63 | public static int indexOf(Parent p, Content child) {
64 | return p.getContent().indexOf(child);
65 | }
66 |
67 | public static Map getInScopeNamespaceDeclarations(Element e) {
68 | Map prefixToUri = new HashMap();
69 |
70 | for (; e != null; e = e.getParentElement()) {
71 | putIfAbsent(prefixToUri, e.getNamespace());
72 |
73 | for (Object o : e.getAdditionalNamespaces()) {
74 | putIfAbsent(prefixToUri, (Namespace) o);
75 | }
76 | }
77 |
78 | return prefixToUri;
79 | }
80 |
81 | @SuppressWarnings("unchecked")
82 | public static List getChildren(Element e) {
83 | return (List) e.getChildren();
84 | }
85 |
86 | @SuppressWarnings("unchecked")
87 | public static List getChildren(Element e, String name) {
88 | return (List) e.getChildren(name);
89 | }
90 |
91 | /* NOT ATOMIC */
92 | private static boolean putIfAbsent(Map map, K key, V value) {
93 | if (map.containsKey(key)) {
94 | return false;
95 | }
96 | map.put(key, value);
97 | return true;
98 | }
99 |
100 | private static boolean putIfAbsent(Map prefixToUri, Namespace ns) {
101 | if (ns == null) {
102 | return false;
103 | }
104 | return putIfAbsent(prefixToUri, ns.getPrefix(), ns.getURI());
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/logging/ConsoleLogger.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.internal.logging;
2 |
3 | public class ConsoleLogger implements XmlPatchLogger {
4 | @Override
5 | public void info(String s) {
6 | System.out.println(s);
7 | }
8 |
9 | @Override
10 | public void warn(String s) {
11 | System.err.println("WARN: " + s);
12 | }
13 |
14 | @Override
15 | public void error(String s) {
16 | System.err.println("ERROR: " + s);
17 | }
18 |
19 | @Override
20 | public void debug(String s) {
21 | System.err.println("DEBUG: " + s);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/logging/Slf4jLogger.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.internal.logging;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | public class Slf4jLogger implements XmlPatchLogger {
7 | private static Logger logger = LoggerFactory.getLogger("com.github.dnault.xmlpatch");
8 |
9 | public void info(String s) {
10 | logger.info(s);
11 | }
12 |
13 | public void warn(String s) {
14 | logger.warn(s);
15 | }
16 |
17 | public void error(String s) {
18 | logger.error(s);
19 | }
20 |
21 | @Override
22 | public void debug(String s) {
23 | logger.debug(s);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/dnault/xmlpatch/internal/logging/XmlPatchLogger.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.internal.logging;
2 |
3 | public interface XmlPatchLogger {
4 | void info(String s);
5 |
6 | void warn(String s);
7 |
8 | void error(String s);
9 |
10 | void debug(String s);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/commons-io-LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/commons-io-NOTICE.txt:
--------------------------------------------------------------------------------
1 | Apache Commons IO
2 | Copyright 2002-2012 The Apache Software Foundation
3 |
4 | This product includes software developed by
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/jaxen-LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | $Id: LICENSE.txt 1128 2006-02-05 21:49:04Z elharo $
3 |
4 | Copyright 2003-2006 The Werken Company. All Rights Reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are
8 | met:
9 |
10 | * Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in the
15 | documentation and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the Jaxen Project nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
25 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
33 | */
34 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/jdom-LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*--
2 |
3 | Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions
8 | are met:
9 |
10 | 1. Redistributions of source code must retain the above copyright
11 | notice, this list of conditions, and the following disclaimer.
12 |
13 | 2. Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions, and the disclaimer that follows
15 | these conditions in the documentation and/or other materials
16 | provided with the distribution.
17 |
18 | 3. The name "JDOM" must not be used to endorse or promote products
19 | derived from this software without prior written permission. For
20 | written permission, please contact .
21 |
22 | 4. Products derived from this software may not be called "JDOM", nor
23 | may "JDOM" appear in their name, without prior written permission
24 | from the JDOM Project Management .
25 |
26 | In addition, we request (but do not require) that you include in the
27 | end-user documentation provided with the redistribution and/or in the
28 | software itself an acknowledgement equivalent to the following:
29 | "This product includes software developed by the
30 | JDOM Project (http://www.jdom.org/)."
31 | Alternatively, the acknowledgment may be graphical using the logos
32 | available at http://www.jdom.org/images/logos.
33 |
34 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
35 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
36 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37 | DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
38 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
40 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
41 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
42 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
43 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
44 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 | SUCH DAMAGE.
46 |
47 | This software consists of voluntary contributions made by many
48 | individuals on behalf of the JDOM Project and was originally
49 | created by Jason Hunter and
50 | Brett McLaughlin . For more information
51 | on the JDOM Project, please see .
52 |
53 | */
54 |
55 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/jdom-info.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | JDOM
4 | 2.0.6, built February 15 2015
5 |
6 | JDOM is a Java-oriented object model which models XML documents.
7 | It provides a Java-centric means of generating and manipulating
8 | XML documents. While JDOM interoperates well with existing
9 | standards such as the Simple API for XML (SAX) and the Document
10 | Object Model (DOM), it is not an abstraction layer or
11 | enhancement to those APIs. Rather, it seeks to provide a robust,
12 | light-weight means of reading and writing XML data without the
13 | complex and memory-consumptive options that current API
14 | offerings provide.
15 |
16 | 2000-2015, Jason Hunter
17 | BSD/Apache style, see LICENSE.txt
18 | See the jdom-interest mailing list at jdom.org,
19 | searchable at http://jdom.markmail.org
20 | http://www.jdom.org/
21 |
25 |
26 | Jason Hunter (primary, co-creator)
27 |
28 |
29 | Brett McLaughlin (co-creator)
30 |
31 |
32 | Rolf Lear (primary, maintainer)
33 |
34 |
35 | Steven Gould
36 |
37 |
38 | Alex Chaffee
39 |
40 |
41 | Jon Baer
42 |
43 |
44 | Elliotte Rusty Harold
45 |
46 |
47 | Dan Schaffer
48 |
49 |
50 | Fred Trimble
51 |
52 |
53 | Jason Reid
54 |
55 |
56 | Kevin Regan
57 |
58 |
59 | Lucas Gonze
60 |
61 |
62 | Matthew Merlo
63 |
64 |
65 | Philip Nelson
66 |
67 |
68 | Wesley Biggs
69 |
70 |
71 | Wolfgang Werner
72 |
73 |
74 | Yusuf Goolamabbas
75 |
76 |
77 | Brad Huffman
78 |
79 |
80 | Victor Toni
81 |
82 |
83 |
84 |
98 |
99 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/xml-patch/joptsimple-LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License
3 |
4 | Copyright (c) 2004-2015 Paul R. Holser, Jr.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining
7 | a copy of this software and associated documentation files (the
8 | "Software"), to deal in the Software without restriction, including
9 | without limitation the rights to use, copy, modify, merge, publish,
10 | distribute, sublicense, and/or sell copies of the Software, and to
11 | permit persons to whom the Software is furnished to do so, subject to
12 | the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | */
--------------------------------------------------------------------------------
/src/main/resources/com/github/dnault/xmlpatch/antlib.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/filter/multi/XmlMultiPatchTest.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.filter.multi;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.io.Reader;
6 | import java.io.StringReader;
7 | import java.io.StringWriter;
8 |
9 | import com.github.dnault.xmlpatch.batch.AssembledPatch;
10 | import org.apache.commons.io.IOUtils;
11 | import org.jdom2.Element;
12 | import org.junit.Test;
13 |
14 | public class XmlMultiPatchTest {
15 | @Test
16 | public void testBuildFilterChain() throws Exception {
17 | StringReader source = new StringReader(" ");
18 |
19 | AssembledPatch patch = new AssembledPatch();
20 | patch.addDif(new Element("diff").setAttribute("file", "foo/bar.xml")
21 | .addContent(new Element("add").setAttribute("sel", "doc").addContent(new Element("child"))));
22 |
23 | patch.addDif(new Element("diff").setAttribute("file", "foo/bar.xml")
24 | .addContent(new Element("add").setAttribute("sel", "doc/child").addContent(new Element("grandchild"))));
25 |
26 | patch.addDif(new Element("diff").setAttribute("file", "other/bar.xml")
27 | .addContent(new Element("add").setAttribute("sel", "doc/child").addContent(new Element("grandchild"))));
28 |
29 | Reader patched = new XmlMultiPatch(source).buildFilterChain(source, "foo/bar.xml", patch);
30 |
31 | StringWriter result = new StringWriter();
32 |
33 | IOUtils.copy(patched, result);
34 |
35 | assertEquals("\n \n", result.toString());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/AddAttributeTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.test;
18 |
19 | import static com.github.dnault.xmlpatch.test.TestHelper.DECLARATION;
20 | import static com.github.dnault.xmlpatch.test.TestHelper.EOL;
21 | import static com.github.dnault.xmlpatch.test.TestHelper.doPatch;
22 | import static com.github.dnault.xmlpatch.test.TestHelper.doPatchExpectError;
23 | import static com.github.dnault.xmlpatch.test.TestHelper.makeDiff;
24 | import static com.github.dnault.xmlpatch.test.TestHelper.makeDiffWithNamespace;
25 | import static org.junit.Assert.fail;
26 |
27 | import com.github.dnault.xmlpatch.ErrorCondition;
28 | import org.junit.Ignore;
29 | import org.junit.Test;
30 |
31 | public class AddAttributeTest {
32 |
33 | private static final String COMMON_TARGET = DECLARATION +
34 | "" + EOL +
35 | " This is a sample document " + EOL +
36 | "This is a new child ";
37 |
38 | @Test
39 | public void addAttribute() throws Exception {
40 |
41 | String diff = makeDiff("Bob ");
42 |
43 | String expectedResult = DECLARATION +
44 | "" + EOL +
45 | " This is a sample document " + EOL +
46 | "This is a new child ";
47 |
48 | doPatch(COMMON_TARGET, diff, expectedResult);
49 | }
50 |
51 | @Test
52 | public void addAttributeQualifiedWithInvalidPrefix() throws Exception {
53 |
54 | String diff = makeDiff("Bob ");
55 |
56 | doPatchExpectError(COMMON_TARGET, diff, ErrorCondition.INVALID_NAMESPACE_PREFIX);
57 | }
58 |
59 | @Test
60 | @Ignore("known failure")
61 | public void addQualifiedAttributeWithoutDeclaration() throws Exception {
62 | fail();
63 | // todo
64 | // the reference implementation doesn't allow adding qualified attributes
65 | // unless the target document already declares the namespace.
66 | }
67 |
68 | @Test
69 | public void addQualifiedAttribute() throws Exception {
70 |
71 | String target = DECLARATION +
72 | "" + EOL +
73 | " This is a sample document " + EOL +
74 | "This is a new child ";
75 |
76 | String diff = makeDiffWithNamespace("Bob ",
77 | "xmlns:y='urn:foo'");
78 |
79 | String expectedResult = DECLARATION +
80 | "" + EOL +
81 | " This is a sample document " + EOL +
82 | "This is a new child ";
83 |
84 | doPatch(target, diff, expectedResult);
85 | }
86 |
87 | @Test
88 | public void addNamespaceDeclaration() throws Exception {
89 |
90 | String target = DECLARATION +
91 | " ";
92 |
93 | String diff = makeDiff("urn:ns:xxx ");
94 |
95 | String expectedResult = DECLARATION +
96 | " ";
97 |
98 | doPatch(target, diff, expectedResult);
99 | }
100 |
101 | @Test
102 | public void replaceAttribute() throws Exception {
103 | String target = DECLARATION +
104 | "" + EOL +
105 | " This is a sample document " + EOL +
106 | " ";
107 |
108 | String diff = makeDiff("new value ");
109 |
110 | String expectedResult = DECLARATION +
111 | "" + EOL +
112 | " This is a sample document " + EOL +
113 | " ";
114 |
115 | doPatch(target, diff, expectedResult);
116 | }
117 |
118 | @Test
119 | public void replaceAttributeWithEmptyString() throws Exception {
120 | String target = DECLARATION +
121 | "" + EOL +
122 | " This is a sample document " + EOL +
123 | " ";
124 |
125 | String diff = makeDiff(" ");
126 |
127 | String expectedResult = DECLARATION +
128 | "" + EOL +
129 | " This is a sample document " + EOL +
130 | " ";
131 |
132 | doPatch(target, diff, expectedResult);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/AddContentTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.test;
18 |
19 | import static com.github.dnault.xmlpatch.test.TestHelper.*;
20 |
21 | import junit.framework.TestCase;
22 |
23 | import com.github.dnault.xmlpatch.ErrorCondition;
24 | import org.junit.Test;
25 |
26 | public class AddContentTest {
27 |
28 | private static final String COMMON_TARGET = DECLARATION +
29 | "" + EOL +
30 | " This is a sample document " + EOL +
31 | " ";
32 |
33 | @Test
34 | public void appendChildElement() throws Exception {
35 | String newElement = "This is a new child ";
36 | String diff = makeDiff("" + newElement + " ");
37 |
38 | String expectedResult = DECLARATION +
39 | "" + EOL +
40 | " This is a sample document " + EOL +
41 | newElement + " ";
42 |
43 | doPatch(COMMON_TARGET, diff, expectedResult);
44 | }
45 |
46 | @Test
47 | public void preppendChildElement() throws Exception {
48 | String newElement = "This is a new child ";
49 | String diff = makeDiff("" + newElement + " ");
50 |
51 | String expectedResult = DECLARATION +
52 | "" + newElement + EOL +
53 | " This is a sample document " + EOL +
54 | " ";
55 |
56 | doPatch(COMMON_TARGET, diff, expectedResult);
57 | }
58 |
59 | @Test
60 | public void addBeforeElement() throws Exception {
61 | String newElement = "This is a new child ";
62 | String diff = makeDiff("" + newElement + " ");
63 |
64 | String expectedResult = DECLARATION +
65 | "" + EOL +
66 | " " + newElement + "This is a sample document " + EOL +
67 | " ";
68 |
69 | doPatch(COMMON_TARGET, diff, expectedResult);
70 | }
71 |
72 | @Test
73 | public void addAfterElement() throws Exception {
74 | String newElement = "This is a new child ";
75 | String diff = makeDiff("" + newElement + " ");
76 |
77 | String expectedResult = DECLARATION +
78 | "" + EOL +
79 | " This is a sample document " + newElement + EOL +
80 | " ";
81 |
82 | doPatch(COMMON_TARGET, diff, expectedResult);
83 | }
84 |
85 | @Test
86 | public void addCommentBeforeElement() throws Exception {
87 | String newElement = "";
88 | String diff = makeDiff("" + newElement + " ");
89 |
90 | String expectedResult = DECLARATION +
91 | "" + EOL +
92 | " " + newElement + "This is a sample document " + EOL +
93 | " ";
94 |
95 | doPatch(COMMON_TARGET, diff, expectedResult);
96 | }
97 |
98 | @Test
99 | public void addCommentBeforeRoot() throws Exception {
100 | String newElement = "";
101 | String diff = makeDiff("" + newElement + " ");
102 |
103 | String expectedResult = DECLARATION +
104 | newElement + "" + EOL +
105 | " This is a sample document " + EOL +
106 | " ";
107 |
108 | doPatch(COMMON_TARGET, diff, expectedResult);
109 | }
110 |
111 | @Test
112 | public void addElementBeforeRoot() throws Exception {
113 | String newElement = " ";
114 | String diff = makeDiff("" + newElement + " ");
115 |
116 | doPatchExpectError(COMMON_TARGET, diff, ErrorCondition.INVALID_ROOT_ELEMENT_OPERATION);
117 | }
118 |
119 | @Test
120 | public void addNamespaceQualifiedElement() throws Exception {
121 | String newElement = " ";
122 |
123 | String diff = makeDiff("" + newElement + " ");
124 |
125 | String expectedResult = DECLARATION +
126 | "" + EOL +
127 | " This is a sample document " + EOL +
128 | newElement + " ";
129 |
130 | doPatch(COMMON_TARGET, diff, expectedResult);
131 | }
132 |
133 | @Test
134 | public void addNamespaceQualifiedElement2() throws Exception {
135 | String newElement = " ";
136 |
137 | String diff = TestHelper.makeDiffWithNamespace("" + newElement + " ",
138 | "xmlns:x='http://example.com'");
139 |
140 | String expectedResult = DECLARATION +
141 | "" + EOL +
142 | " This is a sample document " + EOL +
143 | " ";
144 |
145 | doPatch(COMMON_TARGET, diff, expectedResult);
146 | }
147 |
148 | @Test
149 | public void addNamespaceQualifiedElement3() throws Exception {
150 |
151 | String target = DECLARATION +
152 | "" + EOL +
153 | " This is a sample document " + EOL +
154 | " ";
155 |
156 | String newElement = " ";
157 |
158 | String diff = makeDiffWithNamespace("" + newElement + " ",
159 | "xmlns:x='http://example.com'");
160 |
161 | String expectedResult = DECLARATION +
162 | "" + EOL +
163 | " This is a sample document " + EOL +
164 | " ";
165 |
166 | doPatch(target, diff, expectedResult);
167 | }
168 |
169 | @Test
170 | public void addNamespaceQualifiedElement4() throws Exception {
171 |
172 | String target = DECLARATION +
173 | "" + EOL +
174 | " This is a sample document " + EOL +
175 | " ";
176 |
177 | String newElement = " ";
178 |
179 | String diff = makeDiffWithNamespace("" + newElement + " ",
180 | "xmlns:x='http://example.com'");
181 |
182 | String expectedResult = DECLARATION +
183 | "" + EOL +
184 | " This is a sample document " + EOL +
185 | " ";
186 |
187 | doPatch(target, diff, expectedResult);
188 | }
189 |
190 | @Test
191 | public void addUnprefixedElementUnderDefaultPrefix() throws Exception {
192 | String target = DECLARATION +
193 | "" + EOL +
194 | " This is a sample document " + EOL +
195 | " ";
196 |
197 | String newElement = " ";
198 |
199 | String diff = makeDiffWithNamespace("" + newElement + " ",
200 | "xmlns:y='uri:default'");
201 |
202 |
203 | String expectedResult = DECLARATION +
204 | "" + EOL +
205 | " This is a sample document " + EOL +
206 | " ";
207 |
208 | doPatch(target, diff, expectedResult);
209 |
210 | }
211 |
212 | @Test
213 | public void addSameNamespaceDifferentPrefix() throws Exception {
214 |
215 | String target = DECLARATION +
216 | "" + EOL +
217 | " This is a sample document " + EOL +
218 | " ";
219 |
220 | String newElement = " ";
221 |
222 | String diff = makeDiffWithNamespace("" + newElement + " ",
223 | "xmlns:y='http://example.com'");
224 |
225 | String expectedResult = DECLARATION +
226 | "" + EOL +
227 | " This is a sample document " + EOL +
228 | " ";
229 |
230 | doPatch(target, diff, expectedResult);
231 | }
232 |
233 | @Test
234 | public void addSameNamespaceDefaultPrefix() throws Exception {
235 | String target = DECLARATION +
236 | "" + EOL +
237 | " This is a sample document " + EOL +
238 | " ";
239 |
240 | String newElement = " ";
241 |
242 | String diff = makeDiffWithNamespace("" + newElement + " ",
243 | "xmlns:y='http://example.com'");
244 |
245 | String expectedResult = DECLARATION +
246 | "" + EOL +
247 | " This is a sample document " + EOL +
248 | " ";
249 |
250 | doPatch(target, diff, expectedResult);
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/DataDrivenTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.test;
18 |
19 | import java.io.InputStream;
20 | import java.util.HashSet;
21 | import java.util.Set;
22 |
23 | import junit.framework.TestCase;
24 | import junit.framework.TestSuite;
25 | import junit.framework.Test;
26 |
27 | import com.github.dnault.xmlpatch.ErrorCondition;
28 | import com.github.dnault.xmlpatch.internal.XmlHelper;
29 |
30 | import org.jdom2.Document;
31 | import org.jdom2.Element;
32 | import org.jdom2.Comment;
33 | import org.jdom2.ProcessingInstruction;
34 | import org.jdom2.filter.Filter;
35 | import org.jdom2.filter.AbstractFilter;
36 |
37 | public class DataDrivenTest extends TestCase {
38 |
39 | private final Document target = new Document();
40 | private final Document diff = new Document();
41 | private final Document expectedResult = new Document();
42 | private final ErrorCondition expectedError;
43 |
44 | private DataDrivenTest(Element e) {
45 | super(e.getAttributeValue("desc"));
46 |
47 | Filter prologFilter = new AbstractFilter() {
48 | @Override
49 | public Object filter(Object o) {
50 | if (o instanceof Element
51 | || o instanceof Comment
52 | || o instanceof ProcessingInstruction) {
53 | return o;
54 | }
55 | return null;
56 | }
57 | };
58 |
59 | target.addContent(XmlHelper.clone(e.getChild("target").getContent(prologFilter)));
60 | diff.setRootElement((Element) e.getChild("diff").clone());
61 | expectedResult.addContent(XmlHelper.clone(e.getChild("result").getContent(prologFilter)));
62 |
63 | String errorName = e.getChild("result").getAttributeValue("error");
64 | expectedError = errorName == null ? null : ErrorCondition.valueOf(errorName);
65 | }
66 |
67 | public static Test suite() {
68 | String resourceName = "regression-test.xml";
69 |
70 | TestSuite suite = new TestSuite(resourceName);
71 |
72 | InputStream is = DataDrivenTest.class.getResourceAsStream(resourceName);
73 | try {
74 | Document d = XmlHelper.parse(is);
75 | Set testNames = new HashSet();
76 |
77 | for (Object o : d.getRootElement().getChildren()) {
78 | Element e = (Element) o;
79 |
80 | String name = e.getAttributeValue("desc");
81 | if (!testNames.add(name)) {
82 | throw new RuntimeException("duplicate test name '" + name + "'");
83 | }
84 |
85 | suite.addTest(new DataDrivenTest(e));
86 | }
87 | } catch (Exception e) {
88 | throw new RuntimeException(e);
89 | }
90 |
91 | return suite;
92 | }
93 |
94 | @Override
95 | protected void runTest() throws Throwable {
96 | if (expectedError != null) {
97 | TestHelper.doPatchExpectError(target, diff, expectedError);
98 | } else {
99 | TestHelper.doPatch(target, diff, expectedResult);
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/ReplaceNamespaceTest.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.test;
2 |
3 | import com.github.dnault.xmlpatch.ErrorCondition;
4 | import org.junit.Ignore;
5 | import org.junit.Test;
6 |
7 | import static com.github.dnault.xmlpatch.test.TestHelper.DECLARATION;
8 | import static com.github.dnault.xmlpatch.test.TestHelper.EOL;
9 | import static com.github.dnault.xmlpatch.test.TestHelper.doPatch;
10 | import static com.github.dnault.xmlpatch.test.TestHelper.doPatchExpectError;
11 | import static com.github.dnault.xmlpatch.test.TestHelper.makeDiff;
12 |
13 | @Ignore("known failure")
14 | public class ReplaceNamespaceTest {
15 |
16 | @Test
17 | public void replaceNamespaceDeclarationUri() throws Exception {
18 | String target = DECLARATION +"sample ";
19 | String diff = makeDiff("urn:new:xxx ");
20 | String expectedResult = DECLARATION + "sample ";
21 | doPatch(target, diff, expectedResult);
22 | }
23 |
24 | @Test
25 | public void replaceNamespaceUriWithEmptyString() throws Exception {
26 | String target = DECLARATION + "sample ";
27 | String diff = makeDiff(" ");
28 | doPatchExpectError(target, diff, ErrorCondition.INVALID_NAMESPACE_URI);
29 | }
30 |
31 | @Test
32 | public void replaceElementsOwnNamespaceDeclarationUri() throws Exception {
33 | String target = DECLARATION +"sample ";
34 | String diff = makeDiff("urn:new:xxx ");
35 | String expectedResult = DECLARATION + "sample ";
36 | doPatch(target, diff, expectedResult);
37 | }
38 |
39 | @Test
40 | public void replaceParentsNamespaceDeclarationUri() throws Exception {
41 | String target = DECLARATION + "sample ";
42 | String diff = makeDiff("urn:new:xxx ");
43 | doPatchExpectError(target, diff, ErrorCondition.UNLOCATED_NODE);
44 | }
45 |
46 | @Test
47 | public void removeNamespaceDeclaration() throws Exception {
48 | String target = DECLARATION +" ";
49 | String diff = makeDiff(" ");
50 | String expectedResult = DECLARATION + " ";
51 | doPatch(target, diff, expectedResult);
52 | }
53 |
54 | @Test
55 | public void removeInUseNamespaceDeclaration() throws Exception {
56 | // spec says of the node to be removed,
57 | // "this prefix MUST NOT be associated with any node
58 | // prior to the removal of this namespace node."
59 |
60 | String target = DECLARATION +
61 | " ";
62 | String diff = makeDiff(" ");
63 | doPatchExpectError(target, diff, ErrorCondition.INVALID_PATCH_DIRECTIVE);
64 | }
65 |
66 | @Test
67 | public void replaceNamespaceDeclaration() throws Exception {
68 | String target = DECLARATION +
69 | "" + EOL +
70 | " This is a sample document " + EOL +
71 | " ";
72 |
73 | String diff = makeDiff("urn:new:xxx ");
74 |
75 | String expectedResult = DECLARATION +
76 | "" + EOL +
77 | " This is a sample document " + EOL +
78 | " ";
79 |
80 | doPatch(target, diff, expectedResult);
81 | }
82 |
83 | @Test
84 | public void replaceNamespaceDeclarationError() throws Exception {
85 | String target = DECLARATION +
86 | "" + EOL +
87 | " This is a sample document " + EOL +
88 | " ";
89 |
90 | String diff = makeDiff("urn:new:xxx ");
91 |
92 | doPatchExpectError(target, diff, ErrorCondition.UNLOCATED_NODE);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/Rfc7351Test.java:
--------------------------------------------------------------------------------
1 | package com.github.dnault.xmlpatch.test;
2 |
3 |
4 | import org.junit.Test;
5 |
6 | public class Rfc7351Test {
7 |
8 | @Test
9 | public void compatibleWithRfc7351PatchOperationNamespace() throws Exception {
10 | String target = "This is a sample document ";
11 | String patch = "\n" +
12 | " This is a new child \n" +
13 | " ";
14 | String expected = "This is a sample document This is a new child ";
15 |
16 | TestHelper.doPatch(target, patch, expected);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/TestHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.test;
18 |
19 | import static org.junit.Assert.fail;
20 | import static org.junit.Assert.assertEquals;
21 |
22 | import java.io.InputStream;
23 | import java.io.ByteArrayInputStream;
24 | import java.io.ByteArrayOutputStream;
25 | import java.io.StringReader;
26 | import java.io.StringWriter;
27 | import java.io.IOException;
28 | import java.io.UnsupportedEncodingException;
29 |
30 | import com.github.dnault.xmlpatch.ErrorCondition;
31 | import com.github.dnault.xmlpatch.PatchException;
32 | import com.github.dnault.xmlpatch.Patcher;
33 | import com.github.dnault.xmlpatch.internal.XmlHelper;
34 |
35 | import org.jdom2.Document;
36 | import org.jdom2.Element;
37 | import org.jdom2.JDOMException;
38 | import org.jdom2.input.SAXBuilder;
39 | import org.jdom2.output.XMLOutputter;
40 |
41 | import org.junit.Assert;
42 |
43 | public class TestHelper {
44 |
45 | public static final String EOL = "\n";
46 | public static final String DECLARATION = "" + EOL;
47 |
48 | public static Document parse(String xml) throws JDOMException, IOException {
49 | SAXBuilder builder = new SAXBuilder();
50 | Document doc = builder.build(new StringReader(xml));
51 | return doc;
52 | }
53 |
54 | public static Document parseClasspathResource(String name) {
55 | InputStream is = TestHelper.class.getResourceAsStream(name);
56 | try {
57 | return XmlHelper.parse(is);
58 | }
59 | catch (Exception e) {
60 | throw new RuntimeException(e);
61 | }
62 | }
63 |
64 | public static String toString(Document d) throws IOException {
65 | XMLOutputter outputter = new XMLOutputter();
66 | StringWriter w = new StringWriter();
67 | outputter.output(d, w);
68 | return w.toString();
69 | }
70 |
71 | public static String toString(Element e) throws IOException {
72 | XMLOutputter outputter = new XMLOutputter();
73 | StringWriter w = new StringWriter();
74 | outputter.output(e, w);
75 | return w.toString();
76 | }
77 |
78 | public static void assertXmlEquals(Document expected, Document actual) throws IOException {
79 | Assert.assertEquals(toString(expected), toString(actual));
80 | }
81 |
82 | public static void doPatch(Document target, Document diff, Document expectedResult) throws Exception {
83 | doPatch(toString(target),toString(diff),toString(expectedResult));
84 | }
85 |
86 | public static void doPatchExpectError(Document target, Document diff, ErrorCondition expectedError) throws Exception {
87 | doPatchExpectError(toString(target), toString(diff), expectedError);
88 | }
89 |
90 | public static void doPatch(String target, String diff, String expectedResult) throws Exception {
91 | ByteArrayOutputStream os = new ByteArrayOutputStream();
92 | Patcher.patch(asStream(target), asStream(diff), os);
93 |
94 | Document expected = XmlHelper.parse(asStream(expectedResult));
95 | Document actual = XmlHelper.parse(new ByteArrayInputStream(os.toByteArray()));
96 | assertXmlEquals(expected, actual);
97 | }
98 |
99 | public static void doPatchExpectError(String target, String diff, ErrorCondition expectedError) throws Exception {
100 | ByteArrayOutputStream os = new ByteArrayOutputStream();
101 | try {
102 | Patcher.patch(asStream(target), asStream(diff), os);
103 | fail("expected error condition: " + expectedError.name() +
104 | "\nbut got:\n" + os.toString("UTF-8"));
105 | } catch (PatchException e) {
106 | if (!expectedError.equals(e.getErrorCondition())) {
107 | e.printStackTrace();
108 | assertEquals(expectedError, e.getErrorCondition());
109 | }
110 | }
111 | }
112 |
113 | private static InputStream asStream(String s) {
114 | try {
115 | return new ByteArrayInputStream(s.getBytes("UTF-8"));
116 | }
117 | catch (UnsupportedEncodingException e) {
118 | throw new RuntimeException(e);
119 | }
120 | }
121 |
122 | public static String makeDiff(String s) {
123 | return DECLARATION + "" + s + " ";
124 | }
125 | public static String makeDiffWithNamespace(String s, String namespace) {
126 | return DECLARATION + "" + s + " ";
127 | }
128 |
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/test/java/com/github/dnault/xmlpatch/test/XmlHelperTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 David Nault and contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.dnault.xmlpatch.test;
18 |
19 | import static com.github.dnault.xmlpatch.internal.XmlHelper.getInScopeNamespaceDeclarations;
20 | import static org.junit.Assert.assertEquals;
21 |
22 | import java.util.HashMap;
23 | import java.util.Map;
24 |
25 | import org.jdom2.Document;
26 | import org.jdom2.Element;
27 | import org.junit.Test;
28 |
29 | public class XmlHelperTest {
30 |
31 | @Test
32 | public void testGetInScopeNamespaceDeclarations() throws Exception {
33 | String xml = "" +
34 | "" +
35 | "" +
36 | " ";
37 |
38 | Document doc = TestHelper.parse(xml);
39 | Element a = doc.getRootElement();
40 | Element b = getFirstChild(a);
41 | Element c = getFirstChild(b);
42 | Element d = getFirstChild(c);
43 |
44 | Map prefixToURI = new HashMap();
45 | prefixToURI.put("", "");
46 | prefixToURI.put("one", "uri:one");
47 | prefixToURI.put("two", "uri:two");
48 | assertEquals(prefixToURI, getInScopeNamespaceDeclarations(a));
49 |
50 | prefixToURI.put("", "uri:default");
51 | assertEquals(prefixToURI, getInScopeNamespaceDeclarations(b));
52 |
53 | prefixToURI.put("two", "uri:anothertwo");
54 | assertEquals(prefixToURI, getInScopeNamespaceDeclarations(c));
55 | assertEquals(prefixToURI, getInScopeNamespaceDeclarations(d));
56 | }
57 |
58 | private Element getFirstChild(Element e) {
59 | return (Element) e.getChildren().get(0);
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/test/resources/com/github/dnault/xmlpatch/test/regression-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | This is a sample document
16 |
17 |
18 |
19 |
20 | This is a new child
21 |
22 |
23 |
24 |
25 | This is a sample document
26 | This is a new child
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | hello
79 |
80 |
81 |
82 |
83 |
84 | urn:ns:xxx
85 |
86 |
87 |
88 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | sample
104 | urn:new:xxx
105 |
106 |
107 |
108 |
109 | sample
110 | one
111 | sample
112 |
113 |
114 |
115 |
116 | 2
117 |
118 |
119 |
120 |
121 | sample
122 |
123 |
124 |
125 |
126 |
127 | sample
128 | replacement
129 | replacement
130 |
131 |
132 |
133 | sample
134 |
135 |
136 |
137 |
138 |
139 | sample
140 |
141 |
142 |
143 |
144 |
145 | sample
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | sample
158 |
159 |
160 |
161 |
162 |
163 | sample
164 |
165 | sample
166 |
167 |
168 |
169 | sample
170 |
171 | sample
172 |
173 |
174 |
175 | sample
176 | replacement
177 | replacement
178 |
179 |
180 |
181 | sample
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | hello
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | hello
273 |
274 |
275 |
276 |
277 |
--------------------------------------------------------------------------------