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 }