1 21 22 package nu.xom.canonical; 23 24 import java.io.IOException ; 25 import java.io.OutputStream ; 26 import java.util.Arrays ; 27 import java.util.Comparator ; 28 29 import nu.xom.Attribute; 30 import nu.xom.Comment; 31 import nu.xom.DocType; 32 import nu.xom.Document; 33 import nu.xom.Element; 34 import nu.xom.Node; 35 import nu.xom.ParentNode; 36 import nu.xom.ProcessingInstruction; 37 import nu.xom.Serializer; 38 import nu.xom.Text; 39 40 56 public class Canonicalizer { 57 58 private boolean withComments; 59 private Serializer serializer; 60 61 private static Comparator comparator = new AttributeComparator(); 62 63 private static class AttributeComparator implements Comparator { 64 65 public int compare(Object o1, Object o2) { 66 Attribute a1 = (Attribute) o1; 67 Attribute a2 = (Attribute) o2; 68 69 String namespace1 = a1.getNamespaceURI(); 70 String namespace2 = a2.getNamespaceURI(); 71 if (namespace1.equals(namespace2)) { 72 return a1.getLocalName().compareTo(a2.getLocalName()); 73 } 74 else if (namespace1.equals("")) { 75 return -1; 76 } 77 else if (namespace2.equals("")) { 78 return 1; 79 } 80 else { return namespace1.compareTo(namespace2); 82 } 83 84 } 85 86 } 87 88 89 98 public Canonicalizer(OutputStream out) { 99 this(out, true); 100 } 101 102 103 114 public Canonicalizer( 115 OutputStream out, boolean withComments) { 116 this.serializer = new CanonicalXMLSerializer(out); 117 serializer.setLineSeparator("\n"); 118 this.withComments = withComments; 119 } 120 121 122 private class CanonicalXMLSerializer extends Serializer { 123 124 135 CanonicalXMLSerializer(OutputStream out) { 136 super(out); 137 setLineSeparator("\n"); 138 } 139 140 141 152 public final void write(Document doc) throws IOException { 153 154 int position = 0; 155 while (true) { 156 Node child = doc.getChild(position); 157 writeChild(child); 158 position++; 159 if (child instanceof ProcessingInstruction) breakLine(); 160 else if (child instanceof Comment && withComments) { 161 breakLine(); 162 } 163 else if (child instanceof Element) break; 164 } 165 166 for (int i = position; i < doc.getChildCount(); i++) { 167 Node child = doc.getChild(i); 168 if (child instanceof ProcessingInstruction) breakLine(); 169 else if (child instanceof Comment && withComments) { 170 breakLine(); 171 } 172 writeChild(child); 173 } 174 175 flush(); 176 } 177 178 179 192 protected final void write(Element element) 193 throws IOException { 194 195 if (element.getChildCount() == 0) { 198 writeStartTag(element, false); 199 writeEndTag(element); 200 } 201 else { 202 Node current = element; 203 boolean end = false; 204 int index = -1; 205 int[] indexes = new int[10]; 206 int top = 0; 207 indexes[0] = -1; 208 while (true) { 209 if (!end && current.getChildCount() > 0) { 210 writeStartTag((Element) current, false); 211 current = current.getChild(0); 212 index = 0; 213 top++; 214 indexes = grow(indexes, top); 215 indexes[top] = 0; 216 } 217 else { 218 if (end) { 219 writeEndTag((Element) current); 220 if (current == element) break; 221 } 222 else { 223 writeChild(current); 224 } 225 end = false; 226 ParentNode parent = current.getParent(); 227 if (parent.getChildCount() - 1 == index) { 228 current = parent; 229 top--; 230 if (current != element) { 231 parent = current.getParent(); 232 index = indexes[top]; 233 } 234 end = true; 235 } 236 else { 237 index++; 238 indexes[top] = index; 239 current = parent.getChild(index); 240 } 241 } 242 } 243 } 244 245 } 246 247 248 private int[] grow(int[] indexes, int top) { 249 250 if (top < indexes.length) return indexes; 251 int[] result = new int[indexes.length*2]; 252 System.arraycopy(indexes, 0, result, 0, indexes.length); 253 return result; 254 255 } 256 257 258 protected void writeStartTag(Element element, boolean isEmpty) 259 throws IOException { 260 writeRaw("<"); 261 writeRaw(element.getQualifiedName()); 262 263 ParentNode parent = element.getParent(); 264 265 Element parentElement = null; 266 if (parent instanceof Element) { 267 parentElement = (Element) parent; 268 } 269 270 for (int i = 0; 271 i < element.getNamespaceDeclarationCount(); 272 i++) { 273 String prefix = element.getNamespacePrefix(i); 274 String uri = element.getNamespaceURI(prefix); 275 if (parentElement != null) { 276 if (uri.equals( 277 parentElement.getNamespaceURI(prefix))) { 278 continue; 279 } 280 } 281 else if (uri.equals("")) { 282 continue; } 284 285 writeRaw(" "); 286 writeNamespaceDeclaration(prefix, uri); 287 } 288 289 Attribute[] sorted = sortAttributes(element); 290 for (int i = 0; i < sorted.length; i++) { 291 writeRaw(" "); 292 write(sorted[i]); 293 } 294 295 writeRaw(">"); 296 } 297 298 299 protected void write(Attribute attribute) throws IOException { 300 writeRaw(attribute.getQualifiedName()); 301 writeRaw("=\""); 302 writeRaw(prepareAttributeValue(attribute)); 303 writeRaw("\""); 304 } 305 306 307 protected void writeEndTag(Element element) throws IOException { 308 writeRaw("</"); 309 writeRaw(element.getQualifiedName()); 310 writeRaw(">"); 311 } 312 313 314 private Attribute[] sortAttributes(Element element) { 315 316 Attribute[] result 317 = new Attribute[element.getAttributeCount()]; 318 for (int i = 0; i < element.getAttributeCount(); i++) { 319 result[i] = element.getAttribute(i); 320 } 321 Arrays.sort(result, comparator); 322 323 return result; 324 325 } 326 327 328 private String prepareAttributeValue(Attribute attribute) { 329 330 String value = attribute.getValue(); 331 StringBuffer result = new StringBuffer (value.length()); 332 333 if (attribute.getType().equals(Attribute.Type.CDATA) 334 || attribute.getType().equals(Attribute.Type.UNDECLARED)) { 335 char[] data = value.toCharArray(); 336 for (int i = 0; i < data.length; i++) { 337 char c = data[i]; 338 if (c == '\t') { 339 result.append("	"); 340 } 341 else if (c == '\n') { 342 result.append("
"); 343 } 344 else if (c == '\r') { 345 result.append("
"); 346 } 347 else if (c == '\"') { 348 result.append("""); 349 } 350 else if (c == '&') { 351 result.append("&"); 352 } 353 else if (c == '<') { 354 result.append("<"); 355 } 356 else { 357 result.append(c); 358 } 359 } 360 } 361 else { 362 char[] data = value.toCharArray(); 370 boolean seenFirstNonSpace = false; 371 for (int i = 0; i < data.length; i++) { 372 if (data[i] == ' ') { 373 if (i != data.length-1 && data[i+1] != ' ' && seenFirstNonSpace) { 374 result.append(data[i]); 375 } 376 continue; 377 } 378 seenFirstNonSpace = true; 379 if (data[i] == '\t') { 380 result.append("	"); 381 } 382 else if (data[i] == '\n') { 383 result.append("
"); 384 } 385 else if (data[i] == '\r') { 386 result.append("
"); 387 } 388 else if (data[i] == '\"') { 389 result.append("""); 390 } 391 else if (data[i] == '&') { 392 result.append("&"); 393 } 394 else if (data[i] == '<') { 395 result.append("<"); 396 } 397 else { 398 result.append(data[i]); 399 } 400 } 401 } 402 403 return result.toString(); 404 405 } 406 407 408 423 protected final void write(Text text) throws IOException { 424 String input = text.getValue(); 425 StringBuffer result = new StringBuffer (input.length()); 426 for (int i = 0; i < input.length(); i++) { 427 char c = input.charAt(i); 428 if (c == '\r') { 429 result.append("
"); 430 } 431 else if (c == '&') { 432 result.append("&"); 433 } 434 else if (c == '<') { 435 result.append("<"); 436 } 437 else if (c == '>') { 438 result.append(">"); 439 } 440 else { 441 result.append(c); 442 } 443 } 444 writeRaw(result.toString()); 445 } 446 447 448 461 protected final void write(Comment comment) throws IOException { 462 if (withComments) super.write(comment); 463 } 464 465 466 474 protected final void write(DocType doctype) { 475 } 477 478 479 } 480 481 482 493 public final void write(Document doc) throws IOException { 494 serializer.write(doc); 495 serializer.flush(); 496 } 497 498 499 } | Popular Tags |