2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.xml.xdm.diff;
44
import java.io.IOException;
45
import java.io.UnsupportedEncodingException;
46
import java.util.ArrayList;
47
import java.util.HashMap;
48
import java.util.List;
50
import javax.swing.text.BadLocationException;
51
import org.netbeans.editor.BaseDocument;
52
import org.netbeans.modules.xml.text.syntax.XMLKit;
53
import org.netbeans.modules.xml.xam.ModelSource;
54
import org.netbeans.modules.xml.xam.dom.ElementIdentity;
55
import org.netbeans.modules.xml.xdm.XDMModel;
56
import org.netbeans.modules.xml.xdm.nodes.Attribute;
57
import org.netbeans.modules.xml.xdm.nodes.Element;
58
import org.netbeans.modules.xml.xdm.nodes.Node;
59
import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
60
import org.netbeans.modules.xml.xdm.nodes.Text;
61
import org.netbeans.modules.xml.xdm.diff.Change.AttributeChange;
62
import org.netbeans.modules.xml.xdm.visitor.PositionFinderVisitor;
63
import org.openide.util.Lookup;
64
import org.openide.util.lookup.Lookups;
65
import javax.swing.text.Document;
66
import org.netbeans.modules.xml.xdm.nodes.Token;
67
import org.w3c.dom.NamedNodeMap;
68
import org.w3c.dom.NodeList;
74
public class XDMUtil {
76
public enum ComparisonCriteria {
82
* Constructor for XDMUtil
88
* returns a pretty print version of xml doc, using given indentation
90
public String prettyPrintXML(String doc, String indentation)
91
throws UnsupportedEncodingException, IOException, BadLocationException {
92
Document sd1 = new BaseDocument(XMLKit.class, false);
93
XDMModel m1 = createXDMModel(sd1, doc);
94
Node root1 = m1.getDocument();
96
Document sd2 = new BaseDocument(XMLKit.class, false);
97
XDMModel m2 = createXDMModel(sd2);
99
m2.setIndentation(indentation);
101
Node root2 = m2.getDocument();
103
root2 = doPrettyPrint(m2, root2, root1);
106
String prettyXMLStr = sd2.getText(0, sd2.getLength());
108
int firstChildPos1 = -1;
109
Node firstChild1 = (Node) root1.getChildNodes().item(0);
110
if(firstChild1 != null)
111
firstChildPos1 = new PositionFinderVisitor().findPosition(
112
m1.getDocument(), firstChild1);
114
int firstChildPos2 = -1;
115
Node firstChild2 = (Node) root2.getChildNodes().item(0);
116
if(firstChild2 != null)
117
firstChildPos2 = new PositionFinderVisitor().findPosition(
118
m2.getDocument(), firstChild2);
120
return (firstChildPos1==-1?doc:(sd1.getText(0, firstChildPos1) +
121
sd2.getText(firstChildPos2, sd2.getLength() - firstChildPos2)));
125
* compares 2 xml document contents using a criteria
130
* ComparisonCriteria.EQUAL - means Two documents are considered to be
131
* equal if they contain the same elements and attributes regardless
133
* ComparisonCriteria.IDENTICAL - means Two documents are considered to
134
* be "identical" if they contain the same elements and attributes in
138
* Namespace attribute diffs
139
* Namespace attribute prefix diffs
140
* Attribute whitespace diffs
142
* Use the next API (4 argument compareXML api) if you do not want to filter
143
* all of the above 4 types of diffs
145
public List<Difference> compareXML(String xml1, String xml2,
146
XDMUtil.ComparisonCriteria criteria)
148
return compareXML(xml1, xml2, criteria, true);
152
* compares 2 xml document contents using a criteria
157
* ComparisonCriteria.EQUAL - means Two documents are considered to be
158
* equal if they contain the same elements and attributes regardless
160
* ComparisonCriteria.IDENTICAL - means Two documents are considered to
161
* be "identical" if they contain the same elements and attributes in
163
* @param ignoreWhiteSpace - filters whitespace diffs
165
public List<Difference> compareXML(String firstDoc,
166
String secondDoc, ComparisonCriteria type, boolean filterWhiteSpace)
167
throws BadLocationException, IOException {
168
Document sd1 = new BaseDocument(XMLKit.class, false);
169
XDMModel m1 = createXDMModel(sd1);
170
sd1.remove(0, XML_PROLOG.length());
171
sd1.insertString(0, firstDoc, null);
173
fDoc = m1.getDocument();
175
Document sd2 = new BaseDocument(XMLKit.class, false);
176
sd2.getText(0, sd2.getLength());
177
XDMModel m2 = createXDMModel(sd2);
178
sd2.remove(0, XML_PROLOG.length());
179
sd2.insertString(0, secondDoc, null);
182
sDoc = m2.getDocument();
184
XDUDiffFinder dif = new XDUDiffFinder(createElementIdentity());
185
List<Difference> diffs = dif.findDiff(m1.getDocument(), m2.getDocument());
187
diffs = XDUDiffFinder.filterWhitespace(diffs);//filter whitespace diffs
188
if(type == ComparisonCriteria.EQUAL) {//remove order change diffs
189
List<Difference> filteredDiffs = new ArrayList<Difference>();
190
for(Difference d:diffs) {
191
if(d instanceof Change) {
192
Change c = (Change)d;
193
if(c.isPositionChanged())//node (element/text) pos change
194
if(!c.isTokenChanged() && !c.isAttributeChanged())
196
if(c.isAttributeChanged() && !c.isTokenChanged()) {//attr change only
197
List<Change.AttributeDiff> removeList =
198
new ArrayList<Change.AttributeDiff>();
199
List<Change.AttributeDiff> attrChanges = c.getAttrChanges();
200
for(int i=0;i<attrChanges.size();i++) {
201
if(attrChanges.get(i) instanceof Change.AttributeChange) {
202
Change.AttributeChange ac =
203
(Change.AttributeChange) attrChanges.get(i);
204
if(ac.isPositionChanged() && !ac.isTokenChanged())//attr pos change only
208
for(int i=0;i<removeList.size();i++)
209
c.removeAttrChanges(removeList.get(i));
210
if(c.getAttrChanges().size() == 0) //filter this diff
213
filteredDiffs.add(d);
215
filteredDiffs.add(d);
218
return filteredDiffs;
221
//remove pseudo attr position changes
222
removePseudoAttrPosChanges(diffs);
224
filterSchemaLocationDiffs(diffs);
229
private ElementIdentity createElementIdentity() {
230
//Establish DOM element identities
231
ElementIdentity eID = new XDElementIdentity();
232
//Following values are suitable for Schema and WSDL documents
233
//these default values can be reset by eID.reset() call
234
eID.addIdentifier( "id" );
235
eID.addIdentifier( "name" );
236
eID.addIdentifier( "ref" );
240
private XDMModel createXDMModel(Document sd)
241
throws BadLocationException, IOException {
242
return createXDMModel(sd, "");
245
private XDMModel createXDMModel(Document sd, String content)
246
throws BadLocationException, IOException {
247
boolean foundXMLProlog = true;
248
if(content.indexOf("<?xml") == -1) //insert xml prolog, otherwise XMLSyntaxParser will fail
249
sd.insertString(0, XML_PROLOG+content, null);
251
sd.insertString(0, content, null);
252
Lookup lookup = Lookups.singleton(sd);
253
ModelSource ms = new ModelSource(lookup, true);
254
XDMModel model = new XDMModel(ms);
260
private Node doPrettyPrint(XDMModel m2, Node n2, Node n1) {
262
NodeList childs1 = n1.getChildNodes();
264
for(int i=0;i<childs1.getLength();i++) {
265
n1 = (NodeImpl) childs1.item(i);
266
newNode = ((NodeImpl)n1).cloneNode(true, false);
267
List<Node> ancestors = m2.add(n2, newNode, count++);
268
n2 = ancestors.get(0);
270
List<Node> ancestors = new ArrayList<Node>();
271
fixPrettyText(m2, n2, ancestors, "");
272
n2 = ancestors.get(0);
276
private void fixPrettyText(XDMModel m, final Node n, List<Node> ancestors, String indent) {
278
int index = m.getIndentation().length();
279
NodeList childs = parent.getChildNodes();
280
List<Node> visitList = new ArrayList<Node>();
281
for(int i=0;i<childs.getLength();i++) {
282
Node child = (Node) childs.item(i);
283
if(checkPrettyText(child)) {
284
Text txt = (Text) ((NodeImpl)child).cloneNode(true);
285
if(i < childs.getLength()-1 || ancestors.size() == 0)
286
txt.setText("\n"+indent);
288
String lastTextIndent = "\n";
289
if(m.getIndentation().length() < indent.length() )
290
lastTextIndent += indent.substring(m.getIndentation().length());
291
txt.setText(lastTextIndent);
293
List<Node> ancestors2 = m.modify(child, txt);
294
parent = ancestors2.get(0);
296
else if(childs.item(i) instanceof Element)
297
visitList.add((Node)childs.item(i));
299
ancestors.add(parent);
300
for(int i=0;i<visitList.size();i++) {
301
fixPrettyText(m, (Node)visitList.get((i)), ancestors, indent+m.getIndentation());
303
visitList.clear(); //no need to keep it beyond here
306
public static boolean checkPrettyText(Node txt) {
307
if (txt instanceof Text) {
308
if ((((NodeImpl)txt).getTokens().size() == 1) &&
309
isWhitespaceOnly(((NodeImpl)txt).getTokens().get(0).getValue())) {
316
public static boolean isWhitespaceOnly(String tn) {
317
return XDUDiffFinder.isPossibleWhiteSpace(tn) &&
318
tn.trim().length() == 0;
322
public static int findPosition(final Node n) {
323
return new PositionFinderVisitor().findPosition(
324
(Node)n.getOwnerDocument(), n);
328
* filters or removes diffs that are attr position changes
330
public static void removePseudoAttrPosChanges(final List<Difference> diffs) {
331
List<Difference> removeDiffs = new ArrayList<Difference>();
332
for(Difference dif:diffs) {
333
if(dif instanceof Change) {
334
Change c = (Change)dif;
335
//filter attibute position changes only
336
if(c.isAttributeChanged() && !c.isPositionChanged() && !c.isTokenChanged()) {
337
List<Change.AttributeDiff> attrdiffs = c.getAttrChanges();
338
int size = attrdiffs.size();
339
List<Change.AttributeDiff> removeAttrs = new ArrayList<Change.AttributeDiff>();
342
for(Change.AttributeDiff attrdif:attrdiffs) {
343
if(attrdif instanceof Change.AttributeDelete)
345
else if(attrdif instanceof Change.AttributeAdd)
347
else if(attrdif instanceof Change.AttributeChange) {
348
Change.AttributeChange attrChange =
349
(AttributeChange) attrdif;
350
if(attrChange.isPositionChanged() && !attrChange.isTokenChanged()) {
351
if((attrChange.getOldAttributePosition() - delCount + addCount) ==
352
attrChange.getNewAttributePosition())
353
removeAttrs.add(attrdif);
357
for(Change.AttributeDiff attrdif:removeAttrs) {
358
c.removeAttrChanges(attrdif);
360
if(size > 0 && c.getAttrChanges().size() == 0)
361
removeDiffs.add(dif);
365
for(Difference dif:removeDiffs) {
371
* filters or removes diffs that are attr position changes
373
public static void filterAttributeOrderChange(final List<Difference> diffs) {
374
List<Difference> removeDiffs = new ArrayList<Difference>();
375
for(Difference dif:diffs) {
376
if(dif instanceof Change) {
377
Change c = (Change)dif;
378
//filter attibute position changes only
379
if(c.isAttributeChanged() && !c.isPositionChanged() && !c.isTokenChanged()) {
380
List<Change.AttributeDiff> attrdiffs = c.getAttrChanges();
381
int size = attrdiffs.size();
382
List<Change.AttributeDiff> removeAttrs = new ArrayList<Change.AttributeDiff>();
383
for(Change.AttributeDiff attrdif:attrdiffs) {
384
if(attrdif instanceof Change.AttributeChange) {
385
Change.AttributeChange attrChange =
386
(AttributeChange) attrdif;
387
if(attrChange.isPositionChanged() && !attrChange.isTokenChanged())
388
removeAttrs.add(attrdif);
391
for(Change.AttributeDiff attrdif:removeAttrs) {
392
c.removeAttrChanges(attrdif);
394
if(size > 0 && c.getAttrChanges().size() == 0)
395
removeDiffs.add(dif);
399
for(Difference dif:removeDiffs) {
405
* filters or removes diffs that are schemalocation attr "xsi:schemaLocation='some url'"
407
public static void filterSchemaLocationDiffs(final List<Difference> diffs) {
408
List<Difference> removeDiffs = new ArrayList<Difference>();
409
for(Difference dif:diffs) {
410
if(dif instanceof Change) {
411
Change c = (Change)dif;
412
//filter namespace attibute changes only
413
if(c.isAttributeChanged() && !c.isPositionChanged() &&
414
!c.isTokenChanged() && removeSchemaLocationAttrDiffs(c)) {
415
removeDiffs.add(dif);
419
for(Difference dif:removeDiffs) {
425
* removes attr diffs that are ns attr "prefix:schemaLocation='some url'"
427
public static boolean removeSchemaLocationAttrDiffs(Change c) {
428
List<Change.AttributeDiff> attrdiffs = c.getAttrChanges();
429
int size = attrdiffs.size();
430
List<Change.AttributeDiff> removeAttrs = new ArrayList<Change.AttributeDiff>();
431
for(Change.AttributeDiff attrdif:attrdiffs) {
432
Attribute oldAttr = attrdif.getOldAttribute();
433
Attribute newAttr = attrdif.getNewAttribute();
434
if(oldAttr != null && oldAttr.getName().endsWith(SCHEMA_LOCATION))
435
removeAttrs.add(attrdif);
436
else if(newAttr != null && newAttr.getName().endsWith(SCHEMA_LOCATION))
437
removeAttrs.add(attrdif);
439
for(Change.AttributeDiff attrdif:removeAttrs) {
440
c.removeAttrChanges(attrdif);
442
if(size > 0 && attrdiffs.size() == 0)
447
public class XDElementIdentity extends DefaultElementIdentity {
450
* Creates a new instance of DefaultElementIdentity
452
public XDElementIdentity() {
456
protected boolean compareElement(org.w3c.dom.Element n1, org.w3c.dom.Element n2, org.w3c.dom.Node parent1, org.w3c.dom.Document doc1, org.w3c.dom.Document doc2) {
457
String qName1 = n1.getLocalName();
458
String qName2 = n2.getLocalName();
459
String ns1 = ((Node)n1).getNamespaceURI((org.netbeans.modules.xml.xdm.nodes.Document) doc1);
460
String ns2 = ((Node)n2).getNamespaceURI((org.netbeans.modules.xml.xdm.nodes.Document) doc2);
462
if ( qName1.intern() != qName2.intern() )
464
if(!((ns1 == null || ns1.equals("")) && (ns2 == null || ns2.equals("")))) {//can determine ns
465
if ( !(ns1 == null && ns2 == null) &&
466
!(ns1 != null && ns2 != null && ns1.intern() == ns2.intern() ) )
470
if(parent1 == doc1) return true; //if root no need to compare other identifiers
472
return compareAttr( n1, n2);
476
public class XDUDiffFinder extends DiffFinder {
478
public XDUDiffFinder(ElementIdentity eID) {
482
public List<Change.Type> checkChange(final Node p1, final Node p2) {
483
List<Change.Type> changes = new ArrayList<Change.Type>();
484
if (p1 instanceof Element && p2 instanceof Element) {
485
if ( ! checkAttributesEqual((Element)p1, (Element)p2)) {
486
changes.add(Change.Type.ATTRIBUTE);
492
protected boolean checkAttributesEqual(final Element p1, final Element p2) {
493
if (p1 == null || p2 == null) return false;
494
NamedNodeMap nm1 = p1.getAttributes();
495
NamedNodeMap nm2 = p2.getAttributes();
496
//if( nm1.getLength() != nm2.getLength() ) return false;
498
for ( int i = 0; i < nm1.getLength(); i++ ) {
499
Node attr1 = (Node) nm1.item(i);
500
if(attr1.getNodeName().startsWith("xmlns"))
502
Node attr2 = (Node) nm2.getNamedItem(attr1.getNodeName());
503
if ( attr2 == null ) return false;
504
if(nm2.item(i) != attr2) return false;
505
if(!attr1.getNodeValue().equals(attr2.getNodeValue()))
511
protected boolean compareTextByValue(Text n1, Text n2) {
512
return n1.getNodeValue().equals(n2.getNodeValue());
516
static org.netbeans.modules.xml.xdm.nodes.Document fDoc;
517
static org.netbeans.modules.xml.xdm.nodes.Document sDoc;
519
public final static String NS_PREFIX = "xmlns";
520
public final static String SCHEMA_LOCATION = "schemaLocation";
521
public final static String XML_PROLOG = "<?xml version=\"1.0\"?>\n";