View Javadoc

1   /*
2    * Copyright 2009 - 2010 JEuclid, http://jeuclid.sf.net
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  /* $Id $ */
18  
19  package net.sourceforge.jeuclid.biparser;
20  
21  import org.w3c.dom.Document;
22  import org.w3c.dom.Element;
23  import org.w3c.dom.Node;
24  import org.xml.sax.Attributes;
25  
26  /**
27   * this class is used to store specific information about a composite xml-node.
28   * the node can have one child, many attributes and can be invalid
29   * 
30   * @version $Revision: 0b66106c7ff7 $
31   */
32  public final class BiNode extends AbstractBiNode {
33  
34      /** child node. */
35      private IBiNode child;
36  
37      /** offset to child from node begin (length of open tag). */
38      private final int childOffset;
39  
40      /** if false, node is a valid xml-node. */
41      private boolean invalid;
42  
43      /** DOM-info: namespaceURI. */
44      private String namespaceURI;
45  
46      /** DOM-info: tag-name. */
47      private String eName;
48  
49      /** DOM-info: attributes of node. */
50      private Attributes attrs;
51  
52      /**
53       * creates a new BiNode, length must be set afterwards. constructor does not
54       * create a DOM-node
55       * 
56       * @param co
57       *            offset to child from node begin (length of open tag)
58       * @param ns
59       *            DOM-info
60       * @param n
61       *            DOM-info
62       * @param a
63       *            DOM-info
64       */
65      public BiNode(final int co, final String ns, final String n,
66              final Attributes a) {
67          this.childOffset = co;
68          this.namespaceURI = ns;
69          this.eName = n;
70          this.attrs = a;
71      }
72  
73      /**
74       * get the name of the node (tagname).
75       * 
76       * @return nodename
77       */
78      public String getNodeName() {
79          final String ret;
80  
81          if (this.eName == null) {
82              if (this.getNode() == null) {
83                  ret = null;
84              } else {
85                  ret = this.getNode().getNodeName();
86              }
87          } else {
88              ret = this.eName;
89          }
90  
91          return ret;
92      }
93  
94      /**
95       * add a child to this node, if node has already a child, forward to child.
96       * 
97       * @param c
98       *            new child for this node
99       */
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 }