001 /* 002 * Copyright 2009 - 2010 JEuclid, http://jeuclid.sf.net 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 /* $Id $ */ 018 019 package net.sourceforge.jeuclid.biparser; 020 021 import org.w3c.dom.Document; 022 import org.w3c.dom.Element; 023 import org.w3c.dom.Node; 024 import org.xml.sax.Attributes; 025 026 /** 027 * this class is used to store specific information about a composite xml-node. 028 * the node can have one child, many attributes and can be invalid 029 * 030 * @version $Revision: 0b66106c7ff7 $ 031 */ 032 public final class BiNode extends AbstractBiNode { 033 034 /** child node. */ 035 private IBiNode child; 036 037 /** offset to child from node begin (length of open tag). */ 038 private final int childOffset; 039 040 /** if false, node is a valid xml-node. */ 041 private boolean invalid; 042 043 /** DOM-info: namespaceURI. */ 044 private String namespaceURI; 045 046 /** DOM-info: tag-name. */ 047 private String eName; 048 049 /** DOM-info: attributes of node. */ 050 private Attributes attrs; 051 052 /** 053 * creates a new BiNode, length must be set afterwards. constructor does not 054 * create a DOM-node 055 * 056 * @param co 057 * offset to child from node begin (length of open tag) 058 * @param ns 059 * DOM-info 060 * @param n 061 * DOM-info 062 * @param a 063 * DOM-info 064 */ 065 public BiNode(final int co, final String ns, final String n, 066 final Attributes a) { 067 this.childOffset = co; 068 this.namespaceURI = ns; 069 this.eName = n; 070 this.attrs = a; 071 } 072 073 /** 074 * get the name of the node (tagname). 075 * 076 * @return nodename 077 */ 078 public String getNodeName() { 079 final String ret; 080 081 if (this.eName == null) { 082 if (this.getNode() == null) { 083 ret = null; 084 } else { 085 ret = this.getNode().getNodeName(); 086 } 087 } else { 088 ret = this.eName; 089 } 090 091 return ret; 092 } 093 094 /** 095 * add a child to this node, if node has already a child, forward to child. 096 * 097 * @param c 098 * new child for this node 099 */ 100 public void addChild(final IBiNode c) { 101 // 1st child 102 if (this.child == null) { 103 this.setChild(c); 104 } else { 105 // 2nd - nth child 106 this.child.addSibling(c); 107 } 108 } 109 110 /** 111 * get the child of the node. 112 * 113 * @return child 114 */ 115 public IBiNode getChild() { 116 return this.child; 117 } 118 119 /** 120 * set child for this node. 121 * 122 * @param c 123 * new child for this node 124 */ 125 public void setChild(final IBiNode c) { 126 if (c != null) { 127 c.setPrevious(this); 128 } 129 130 this.child = c; 131 } 132 133 /** 134 * get the type of node. 135 * 136 * @return NODE 137 */ 138 public BiType getType() { 139 return BiType.NODE; 140 } 141 142 /** {@inheritDoc} */ 143 public void insert(final BiTree biTree, final int offset, final int length, 144 final int totalOffset) throws ReparseException, 145 NonIncrementalElementException { 146 // System.out.println("insert " + toString() + " offset=" + offset + 147 // " length=" + length); 148 149 // ---------------- end of this or SIBLING ---------------- 150 if (offset >= this.getLength()) { 151 152 // reparse if node is invalid and start position is at node-end 153 if (offset == this.getLength() && this.invalid) { 154 throw new ReparseException(); 155 } 156 157 // forward to sibling 158 this.forwardToSibling(true, biTree, offset - this.getLength(), 159 length, totalOffset + this.getLength()); 160 161 } else if (this.child != null && !this.invalid 162 && offset >= this.childOffset 163 && offset <= this.childOffset + this.getLengthOfChildren()) { 164 // ---------------- CHILDREN ---------------- 165 try { 166 this.child.insert(biTree, offset - this.childOffset, length, 167 totalOffset + this.childOffset); 168 } catch (final ReparseException ex) { 169 this.parseAndReplace( 170 biTree, 171 biTree.getText().substring(totalOffset, 172 totalOffset + this.getLength() + length), 173 length); 174 } 175 } else if (offset == 0) { 176 // ---------------- before THIS ---------------- 177 throw new ReparseException(); 178 } else { 179 // ---------------- THIS ---------------- 180 this.parseAndReplace( 181 biTree, 182 biTree.getText().substring(totalOffset, 183 totalOffset + this.getLength() + length), length); 184 } 185 } 186 187 /** {@inheritDoc} */ 188 public void remove(final BiTree biTree, final int offset, final int length, 189 final int totalOffset) throws ReparseException, 190 NonIncrementalElementException { 191 // System.out.println("remove " + toString() + " offset=" + 192 // offset + " length=" + length); 193 194 // ---------------- REMOVE THIS ---------------- 195 if (offset == 0 && length >= this.getLength()) { 196 throw new ReparseException(); 197 198 } else if (offset >= this.getLength()) { 199 // ---------------- SIBLING ---------------- 200 this.forwardToSibling(false, biTree, offset - this.getLength(), 201 length, totalOffset + this.getLength()); 202 203 } else if (this.child != null 204 && !this.invalid 205 && offset >= this.childOffset 206 && offset + length <= this.childOffset 207 + this.getLengthOfChildren()) { 208 // ---------------- CHILDREN ---------------- 209 try { 210 this.child.remove(biTree, offset - this.childOffset, length, 211 totalOffset + this.childOffset); 212 } catch (final ReparseException ex) { 213 this.parseAndReplace( 214 biTree, 215 biTree.getText().substring(totalOffset, 216 totalOffset + this.getLength() - length), 217 -length); 218 } 219 } else { 220 // ---------------- THIS ---------------- 221 this.parseAndReplace( 222 biTree, 223 biTree.getText().substring(totalOffset, 224 totalOffset + this.getLength() - length), -length); 225 } 226 } 227 228 /** 229 * set the node as invalid, remove all children. replace node in DOM-tree 230 * with a red '#' 231 * 232 * @param doc 233 * Document to insert the invalid mark '#' 234 */ 235 private void makeInvalidNode(final Document doc) { 236 Element element; 237 238 // create INVALID-textnode in DOM tree 239 element = doc.createElement("mi"); 240 element.setAttribute("mathcolor", "#F00"); 241 element.appendChild(doc.createTextNode("#")); 242 243 if (this.getNode().getParentNode() == null) { 244 doc.replaceChild(element, this.getNode()); 245 } else { 246 this.getNode().getParentNode() 247 .replaceChild(element, this.getNode()); 248 } 249 250 // remove bi-subtree 251 this.setNode(element); 252 this.child = null; 253 this.invalid = true; 254 } 255 256 /** 257 * try to parse the text, if valid replace this node with parsed tree. else 258 * replace this node with invalid mark '#' 259 * 260 * @param biTree 261 * reference to BiTree to which this node contains 262 * @param text 263 * to parse 264 * @param length 265 * change length of this node 266 * @throws NonIncrementalElementException 267 * if the subtree contains an element which cannot be 268 * incrementally updated. 269 */ 270 private void parseAndReplace(final BiTree biTree, final String text, 271 final int length) throws ReparseException, 272 NonIncrementalElementException { 273 BiTree treePart; 274 Node domValid; 275 BiNode parent; 276 final boolean invalidSibling; 277 final boolean invalidPrevious; 278 279 treePart = SAXBiParser.getInstance().parse(text); 280 281 // parse unsuccessful 282 if (treePart == null) { 283 invalidSibling = this.getSibling() != null 284 && this.getSibling().getType() == BiType.NODE 285 && ((BiNode) this.getSibling()).invalid; 286 287 invalidPrevious = this.getPrevious() != null 288 && this.getPrevious().getType() == BiType.NODE 289 && ((BiNode) this.getPrevious()).invalid; 290 291 // if node & previous or node & sibling are invalid - reparse 292 // parent 293 if (invalidPrevious || invalidSibling) { 294 throw new ReparseException(); 295 } 296 297 if (!this.invalid) { 298 this.makeInvalidNode((Document) biTree.getDocument()); 299 } 300 301 this.changeLengthRec(length); 302 303 } else { 304 305 parent = this.getParent(); 306 domValid = treePart.getDOMTree((Document) biTree.getDocument()); 307 treePart.getRoot().addSibling(this.getSibling()); 308 309 // node is root 310 if (parent == null) { 311 312 // no emtpy text 313 if (this.getPrevious() == null) { 314 biTree.setRoot(treePart.getRoot()); 315 } else { 316 // empty text on left side of root 317 this.getPrevious().setSibling(treePart.getRoot()); 318 } 319 320 // replace invalid DOM node 321 biTree.getDocument().replaceChild(domValid, this.getNode()); 322 323 } else { 324 if (this.getPrevious() == parent) { 325 // invalid node is 1st child 326 this.getParent().setChild(treePart.getRoot()); 327 } else { 328 // 2nd - nth child 329 this.getPrevious().setSibling(treePart.getRoot()); 330 } 331 332 // replace invalid DOM node 333 parent.getNode().replaceChild(domValid, this.getNode()); 334 parent.changeLengthRec(length); 335 } 336 337 this.invalid = false; 338 } 339 } 340 341 /** 342 * calculate the length of all children. 343 * 344 * @return length of children 345 */ 346 public int getLengthOfChildren() { 347 int length = 0; 348 IBiNode childTmp; 349 350 if (this.child != null) { 351 // length of first child 352 length += this.child.getLength(); 353 354 childTmp = this.child.getSibling(); 355 while (childTmp != null) { 356 // length of 2nd - nth children 357 length += childTmp.getLength(); 358 childTmp = childTmp.getSibling(); 359 } 360 361 } 362 363 return length; 364 } 365 366 /** 367 * create a DOM-tree from node and all children (recursive). 368 * 369 * @param doc 370 * Document to create DOM-tree 371 * @return root of DOM-tree 372 */ 373 public Node createDOMSubtree(final Document doc) { 374 int i; 375 String aName; 376 Node childNode; 377 Element element; 378 IBiNode tmp; 379 380 element = doc.createElementNS(this.namespaceURI, this.eName); 381 382 // add attributes 383 if (this.attrs != null) { 384 for (i = 0; i < this.attrs.getLength(); i++) { 385 aName = this.attrs.getLocalName(i); 386 387 if ("".equals(aName)) { 388 aName = this.attrs.getQName(i); 389 } 390 391 element.setAttribute(aName, this.attrs.getValue(i)); 392 } 393 } 394 395 // create DOM-tree of children 396 if (this.child != null) { 397 tmp = this.child; 398 399 while (tmp != null) { 400 childNode = tmp.createDOMSubtree(doc); 401 402 if (childNode != null) { 403 element.appendChild(childNode); 404 } 405 406 tmp = tmp.getSibling(); 407 } 408 } 409 410 this.namespaceURI = null; 411 this.eName = null; 412 this.attrs = null; 413 414 this.setNode(element); 415 return element; 416 } 417 418 /** {@inheritDoc} */ 419 @Override 420 public TextPosition searchNode(final Node node, final int totalOffset) { 421 TextPosition result; 422 423 // check if node is this 424 result = super.searchNode(node, totalOffset); 425 426 // forward to child 427 if (result == null && this.child != null) { 428 result = this.child 429 .searchNode(node, totalOffset + this.childOffset); 430 } 431 432 // forward to sibling 433 if (result == null && this.getSibling() != null) { 434 result = this.getSibling().searchNode(node, 435 totalOffset + this.getLength()); 436 } 437 438 return result; 439 } 440 441 @Override 442 public String toString() { 443 final StringBuffer sb = new StringBuffer(32); 444 445 sb.append('['); 446 sb.append(this.invalid ? "INVALID " : ""); 447 sb.append("NODE length: "); 448 sb.append(this.getLength()); 449 450 if (!this.invalid) { 451 sb.append(" <"); 452 sb.append(this.getNodeName()); 453 sb.append("> tag: "); 454 sb.append(this.childOffset); 455 } 456 457 sb.append(']'); 458 459 return sb.toString(); 460 } 461 462 /** {@inheritDoc} */ 463 public String toString(final int level) { 464 final StringBuffer sb = new StringBuffer(32); 465 final String nl = System.getProperty("line.separator"); 466 467 sb.append(this.formatLength()); 468 sb.append(':'); 469 for (int i = 0; i <= level; i++) { 470 sb.append(' '); 471 } 472 473 if (this.invalid) { 474 sb.append("INVALID "); 475 } 476 477 if (!this.invalid) { 478 sb.append('<'); 479 sb.append(this.getNodeName()); 480 sb.append("> tag: "); 481 if (this.childOffset < 100) { 482 sb.append('0'); 483 } 484 if (this.childOffset < 10) { 485 sb.append('0'); 486 } 487 sb.append(this.childOffset); 488 } 489 490 sb.append(nl); 491 492 if (this.child != null) { 493 sb.append(this.child.toString(level + 1)); 494 if (this.getSibling() != null) { 495 sb.append(nl); 496 } 497 498 } 499 500 if (this.getSibling() != null) { 501 sb.append(this.getSibling().toString(level)); 502 } 503 504 return sb.toString(); 505 } 506 }