4A Server -  2.0
 All Classes Namespaces Files Functions Variables Enumerator
DocumentUpdate.java
Go to the documentation of this file.
1 /*
2  * Project: Server for annotations sharing
3  * Author: Ing. Jaroslav Dytrych idytrych@fit.vutbr.cz
4  * File: DocumentUpdate.java
5  * Description: Class with which update document using TextModifications
6  */
7 
8 /**
9  * @file DocumentUpdate.java
10  *
11  * @brief Update of document using TextModifications
12  */
13 
14 /**
15  * @package cz.vutbr.fit.knot.annotations.document
16  *
17  * @brief Classes responsible for handling and updating of document
18  */
19 package cz.vutbr.fit.knot.annotations.document;
20 
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.DocumentFragment;
33 import org.w3c.dom.Element;
34 import org.w3c.dom.Node;
35 
36 /**
37  * Class which update document using TextModifications
38  *
39  * @author Jan Paulovcak (xpaulo00 at stud.fit.vutbr.cz)
40  *
41  * @brief Update of document
42  */
43 public class DocumentUpdate {
44 
45  /**
46  * Exception of DocumentUpdate class which is thrown in case of modification failure
47  *
48  * Contains list of reverse modifications of failed modifications
49  *
50  * @brief Exception of DocumentUpdate class which is thrown in case of modification failure
51  */
52  public static class DocumentUpdateException extends Exception {
53  /** Array of reverse modifications to reverse failed */
54  private ArrayList<TextModification> reverseModifications = new ArrayList<TextModification>();
55  /** Error message */
56  private String errorMessage;
57  /** Flag for resynchronization */
58  private boolean resyncFlag;
59  /** Error code */
60  private Integer errorCode;
61 
62  /**
63  * Adds reverse modification into array
64  *
65  * @param tm Reverse modification
66  */
67  private void addReverseModifications(ArrayList<TextModification> tm){
68  this.reverseModifications.addAll(0,tm);
69  }
70 
71  /**
72  * Gets array of reverse modification
73  *
74  * @return Returns array of reverse modifications
75  */
76  public ArrayList<TextModification> getReverseModifications(){
77  return reverseModifications;
78  }
79 
80  /**
81  * Sets error message
82  *
83  * @param msg Error message
84  */
85  public void setErrorMessage(String msg){
86  this.errorMessage = msg;
87  }
88 
89  /**
90  * Gets error message
91  *
92  * @return Returns error message
93  */
94  public String getErrorMessage(){
95  return errorMessage;
96  }
97 
98  /**
99  * Sets flag for resynchronization
100  *
101  * @param flag Flag for resynchronization
102  */
103  public void setResyncFlag(boolean flag){
104  this.resyncFlag = flag;
105  }
106 
107  /**
108  * Gets flag for resynchronization
109  *
110  * @return Returns flag for resynchronization
111  */
112  public boolean getResyncFlag(){
113  return resyncFlag;
114  }
115 
116  /**
117  * Gets error code
118  *
119  * @return Returns error code
120  */
121  public Integer getErrorCode() {
122  return errorCode;
123  }
124 
125  /**
126  * Sets error code
127  *
128  * @param errorCode Error code
129  */
130  public void setErrorCode(Integer errorCode) {
131  this.errorCode = errorCode;
132  }
133 
134  } // public static class DocumentUpdateException
135 
136  /** Document to update */
137  private Document doc;
138  /** Document to update serialized in string */
139  private String documentString;
140  /** MatcherProvider for additional methods */
142  /** List of modifications to apply */
143  private ArrayList<TextModification> modificationsAL = new ArrayList<TextModification>();
144  /** List of reverse modifications */
145  private ArrayList<TextModification> reverseModifications = new ArrayList<TextModification>();
146  /** Exception to throw */
148  /** Client's session */
150 
151  /**
152  * Constructor
153  *
154  * @param documentString Textual content of document
155  */
157  this.documentString = documentString;
158  this.matcherProvider = new MatcherProvider();
159  }
160 
161  /**
162  * Constructor
163  *
164  * @param documentString Textual content of document
165  * @param edSession Editor's (client's) session
166  */
168  this.documentString = documentString;
169  this.edSession = edSession;
170  this.matcherProvider = new MatcherProvider();
171  }
172 
173  /**
174  * Sets document to update
175  *
176  * @param doc Document
177  */
178  public void setDocument(Document doc){
179  this.doc = doc;
180  }
181 
182  /**
183  * Gets document
184  *
185  * @return Document
186  */
187  public Document getDocument(){
188  return doc;
189  }
190 
191  /**
192  * Sets textual content of document
193  *
194  * @param documentString Textual content of document
195  */
196  public void setDocumentString(String documentString){
197  this.documentString = documentString;
198  }
199 
200  /**
201  * Get document textual content
202  *
203  * @return Document in textual form
204  */
205  public String getDocumentString(){
206  return documentString;
207  }
208 
209  /**
210  * Appends new text modification which will be processed next time on update call.
211  *
212  * @param modification New modification which should be performed to document.
213  */
214  public void appendModification(TextModification modification) {
215  modificationsAL.add(modification);
216  }
217 
218  /**
219  * Appends new text modifications which will be processed next time on update call.
220  *
221  * @param modifications New modifications which should be performed to document.
222  */
223  public void appendModifications(ArrayList<TextModification> modifications) {
224  modificationsAL.addAll(modifications);
225  }
226 
227  /**
228  * Removes text modification from the modifications to be performed.
229  *
230  * @param modification Text modification to be removed.
231  */
232  public void removeModification(TextModification modification) {
233  modificationsAL.remove(modification);
234  }
235 
236  /**
237  * Updates document.
238  *
239  * @throws cz.vutbr.fit.knot.annotations.document.DocumentUpdate.UpdateDocumentException
240  */
242  updateDocument(false, false, modificationsAL);
243  }
244 
245  /**
246  * Updates document.
247  *
248  * @param failed Checks whether modifying has failed
249  * @param reverseRun For reverse run true, false otherwise
250  * @throws cz.vutbr.fit.knot.annotations.document.DocumentUpdate.UpdateDocumentException
251  */
252  public void updateDocument(boolean failed, boolean reverseRun) throws DocumentUpdateException {
253  updateDocument(true, true, modificationsAL);
254  }
255 
256  /**
257  * Updates document.
258  *
259  * @param failed Checks whether modifying has failed
260  * @param reverseRun For reverse run true, false otherwise
261  * @param modifications List of modifications
262  * @throws cz.vutbr.fit.knot.annotations.document.DocumentUpdate.DocumentUpdateException
263  */
264  private void updateDocument(boolean failed, boolean reverseRun, ArrayList<TextModification> modifications) throws DocumentUpdateException {
265 
266  // Nothing to update
267  if (modifications.isEmpty()) {
268  return;
269  }
270 
271  Integer createdReversModsNum = 0;
272 
273  /*
274  * User who uses protocol version 1.x - if modification fails, reverse modification will be added to message for user
275  * system will skip this modification and continues to updating rest of document
276  *
277  * If user is using protocol version 2.0, once modification fails, system will interupt modification process and applies
278  * all reversed modifications to document, to achieve the same version as was before modification process
279  *
280  */
281  boolean skipNContinue = false;
283  skipNContinue = true;
284  }
285 
286  //Creates DOM document from content within documentwrapper
287  try {
288  doc = matcherProvider.getDocumentFromString(documentString, false, false);
289  } catch (Exception ex) {
290  docUpdateEx.setErrorCode(Localisation.ERROR_36_BAD_DOCUMENT);
292  String msg = "Unable to parse document";
293  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
294  }
295  throw docUpdateEx;
296  }
297 
298  if(doc == null){
299  return;
300  }
301 
302  // Normalize document
303  doc.normalizeDocument();
304 
305  /*
306  * Sort modifications
307  * NOTE: Only for protocol v1.x and if failed is equal to false
308  */
309  if (!failed && (edSession.getProtocolLOD() < Constants.PROTOCOL_LOD_V2)) {
310  DocumentUpdateHelper.sortModifications(modifications);
311  }
312 
313  /*
314  * Applying each of modifications
315  */
316  Iterator<TextModification> modsIt;
317  if (!failed) {
318  modsIt = modifications.iterator();
319  } else {
320  modsIt = reverseModifications.iterator();
321  }
322 
323  while (modsIt.hasNext()) {
324  TextModification tm = modsIt.next();
325 
326  //Initialize necessary variables
327  String xPath = tm.getPath();
328  String content = tm.getNewContent();
329 
330  //Search for a node within document
331  Node sourceNode = DocumentUpdateHelper.getDocumentNode(doc, xPath);
332  if(sourceNode == null){
333  /* This is a special case of interrupting modification process
334  * If client is using protocol ver. 1.x and modification fails, resync. message should be sent
335  * to client
336  * Within protocol v2 modification process is interrupted, all changes are reversed
337  */
338  if (skipNContinue) {
339  failed = true;
340  docUpdateEx.setResyncFlag(true);
341  break;
342  } else {
343  docUpdateEx.setErrorMessage("Unable to search for a node");
344  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
345  failed = true;
346  break;
347  }
348  }
349 
350  // Reverse modifications can be generated only in non reverse mode
351  if (!failed) {
352  createdReversModsNum = generateReverseModifications(reverseModifications, tm, sourceNode);
353  if (createdReversModsNum == null) {
354  failed = true;
355  break;
356  }
357  }
358 
359  if (tm.getMode() == Constants.TEXT_MOD_REPLACE) {
360  /*
361  * Replace modification
362  */
363 
364  // Get modifications info
365  Integer offset = tm.getOffset();
366  Integer length = tm.getLength();
367 
368  // Getting parent node
369  Node sourceParentNode = sourceNode.getParentNode();
370  if (sourceParentNode == null) {
371  /*
372  * Element does not have its parent - insertion aborded Choose whether
373  * skip&continue or interrupt modification process
374  */
375  if (skipNContinue) {
376  // Add revers modification of failed modification to exception
377  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
378  continue;
379  } else {
380  docUpdateEx.setErrorMessage("Unable to search for a parent node");
381  docUpdateEx.setErrorCode(Localisation.ERROR_33_MODULE_ERROR);
382  // Remove last generated reverse modifications as, these modifications are not saved to document
383  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
384  failed = true;
385  break;
386  }
387  }
388 
389  /*
390  * Replacement of whole node
391  */
392  if (offset == null && length == null && content != null) {
393 
394  //Create content to replace
395  DocumentFragment fragment;
396  try {
397  fragment = matcherProvider.getFragmentFromString(content);
398  } catch (Exception ex) {
399  String msg = "Unable to parse fragment";
400  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
401  if (skipNContinue) {
402  // Add revers modification of failed modification to exception
403  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
404  continue;
405  } else {
406  docUpdateEx.setErrorMessage("Unable to parse fragment");
407  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
408  // Remove last generated reverse modifications as, these modifications are not saved to document
409  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
410  failed = true;
411  break;
412  }
413  }
414 
415  /*
416  * If content will be empty string, we need to do the same operations
417  * as is done in deletion section Find a node which has at least one
418  * sibling, however maximum depth can be "body" element
419  */
420  if (fragment.getTextContent().isEmpty()) {
421  if (sourceNode.getChildNodes().getLength() < 2) {
422 
423  Node tmpNode = null;
424  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
425  tmpNode = sourceNode;
426  sourceNode = sourceNode.getParentNode();
427  }
428 
429  sourceNode = tmpNode;
430  }
431 
432  //Actualize parent node
433  sourceParentNode = sourceNode.getParentNode();
434 
435  //Remove node
436  sourceParentNode.removeChild(sourceNode);
437  } else {
438  /*
439  * If fragment has some content, classic replacement of node will
440  * take a place
441  */
442 
443  //Import node
444  Node replaceNode = doc.importNode(fragment, true);
445 
446  //Process replace
447  sourceParentNode.replaceChild(replaceNode, sourceNode);
448 
449  //Merge text nodes
450  sourceParentNode.normalize();
451  }
452  } else if (offset == null && length == null && content == null) {
453  /*
454  * Delete whole node
455  */
456 
457  if (sourceNode.getChildNodes().getLength() < 2) {
458 
459  Node tmpNode = null;
460  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
461  tmpNode = sourceNode;
462  sourceNode = sourceNode.getParentNode();
463  }
464 
465  sourceNode = tmpNode;
466  }
467 
468  //Actualize parent node
469  sourceParentNode = sourceNode.getParentNode();
470 
471  //Remove node
472  sourceParentNode.removeChild(sourceNode);
473  } else if (offset != null && length == null && content != null) {
474  /*
475  * Insertion to node
476  * NOTE: Can be applied only on text node
477  */
478 
479  //Checking whether node is text node
480  if (sourceNode.getNodeType() != Node.TEXT_NODE) {
481  if (skipNContinue) {
482  //Add revers modification of failed modification to exception
483  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
484  continue;
485  } else {
486  docUpdateEx.setErrorMessage("Unable to apply modification on non-text node");
487  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
488  //Remove last generated reverse modifications as, these modifications are not saved to document
489  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
490  failed = true;
491  break;
492  }
493  }
494 
495  // Check whether offset is not higher than node text content length
496  if (offset > sourceNode.getTextContent().length()) {
497  if (skipNContinue) {
498  // Add revers modification of failed modification to exception
499  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
500  continue;
501  } else {
502  docUpdateEx.setErrorMessage("Offset extends node text content length");
503  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
504  // Remove last generated reverse modifications as, these modifications are not saved to document
505  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
506  failed = true;
507  break;
508  }
509  }
510 
511  // String containing prepared requested content to parse
512  String tmpStr = sourceNode.getTextContent().substring(0, offset) + content + sourceNode.getTextContent().substring(offset);
513 
514  // Create document fragment from content of requested modification
515  DocumentFragment fragment;
516  try {
517  fragment = matcherProvider.getFragmentFromString(tmpStr);
518  } catch (Exception ex) {
519  String msg = "Unable to parse fragment";
520  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
521  if (skipNContinue) {
522  // Add revers modification of failed modification to exception
523  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
524  continue;
525  } else {
526  docUpdateEx.setErrorMessage("Unable to parse fragment");
527  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
528  //Remove last generated reverse modifications as, these modifications are not saved to document
529  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
530  failed = true;
531  break;
532  }
533  }
534 
535  //Import node
536  Node replaceNode = doc.importNode(fragment, true);
537 
538  //Process replace
539  sourceParentNode.replaceChild(replaceNode, sourceNode);
540 
541  //Merge text nodes
542  sourceParentNode.normalize();
543  } else if (offset != null && length != null && content != null) {
544  /*
545  * Replacement of part of node NOTE: Can be applied only on text node
546  */
547 
548  // Checking whether node is text node
549  if (sourceNode.getNodeType() != Node.TEXT_NODE) {
550  if (skipNContinue) {
551  // Add revers modification of failed modification to exception
552  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
553  continue;
554  } else {
555  docUpdateEx.setErrorMessage("Unable to apply modification on non-text node");
556  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
557  // Remove last generated reverse modifications as, these modifications are not saved to document
558  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
559  failed = true;
560  break;
561  }
562  }
563 
564  // Check whether offset is not higher than node text content length
565  if (offset > sourceNode.getTextContent().length()) {
566  if (skipNContinue) {
567  // Add revers modification of failed modification to exception
568  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
569  continue;
570  } else {
571  docUpdateEx.setErrorMessage("Set modification's offset extends node text content length");
572  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
573  // Remove last generated reverse modifications as, these modifications are not saved to document
574  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
575  failed = true;
576  break;
577  }
578  }
579 
580  // Check whether offset+length is not higher than node text content length
581  if (offset + length > sourceNode.getTextContent().length()) {
582  if (skipNContinue) {
583  // Add revers modification of failed modification to exception
584  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
585  continue;
586  } else {
587  docUpdateEx.setErrorMessage("Set modification's length extends node text content length");
588  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
589  // Remove last generated reverse modifications as, these modifications are not saved to document
590  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
591  failed = true;
592  break;
593  }
594  }
595 
596  /*
597  * If content, that is going to take a place is empty string and it's
598  * offset is set to 0 and length has the same length as source's node
599  * length, will cause, that node will remain empty -> it has to be
600  * removed then
601  *
602  * NOTE: This section should be consulted with supervisor if it's even
603  * possible to achieve this state when content will be empty string If
604  * there is no chance to get into this state, whole 'if' block need to
605  * be removed
606  */
607  if (content.isEmpty() && offset == 0 && length == sourceNode.getTextContent().length()) {
608  if (sourceNode.getChildNodes().getLength() < 2) {
609 
610  Node tmpNode = null;
611  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
612  tmpNode = sourceNode;
613  sourceNode = sourceNode.getParentNode();
614  }
615  sourceNode = tmpNode;
616  }
617 
618  // Actualize parent node
619  sourceParentNode = sourceNode.getParentNode();
620 
621  // Remove node
622  sourceParentNode.removeChild(sourceNode);
623  } else {
624  // String containing prepared requested content to parse
625  String tmpStr = sourceNode.getTextContent().substring(0, offset) + content + sourceNode.getTextContent().substring(offset + length);
626 
627  // Create document fragment from content of requested modification
628  DocumentFragment fragment;
629  try {
630  fragment = matcherProvider.getFragmentFromString(tmpStr);
631  } catch (Exception ex) {
632  String msg = "Unable to parse fragment";
633  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
634  if (skipNContinue) {
635  // Add revers modification of failed modification to exception
636  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
637  continue;
638  } else {
639  docUpdateEx.setErrorMessage("Unable to parse fragment");
640  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
641  // Remove last generated reverse modifications as, these modifications are not saved to document
642  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
643  failed = true;
644  break;
645  }
646  }
647 
648  // Import node
649  Node replaceNode = doc.importNode(fragment, true);
650 
651  // Process replace
652  sourceParentNode.replaceChild(replaceNode, sourceNode);
653 
654  // Merge text nodes
655  sourceParentNode.normalize();
656  }
657  }
658  } else if (tm.getMode() == Constants.TEXT_MOD_INS_BEFORE) {
659  /*
660  * InsertBefore modification
661  */
662 
663  Node sourceParentNode = sourceNode.getParentNode();
664  if(sourceParentNode == null){
665  /*
666  * Element does not have its parent - insertion aborded Choose whether
667  * skip&continue or interrupt modification process
668  */
669  if (skipNContinue) {
670  // Add reverse modification of failed modification to exception
671  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
672  continue;
673  } else {
674  docUpdateEx.setErrorMessage("Unable to search for a parent node");
675  docUpdateEx.setErrorCode(Localisation.ERROR_33_MODULE_ERROR);
676  // Remove last generated reverse modifications as, these modifications are not saved to document
677  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
678  failed = true;
679  break;
680  }
681  }
682 
683  //Create document fragment from content of requested modification
684  DocumentFragment fragment;
685  try {
686  fragment = matcherProvider.getFragmentFromString(content);
687  } catch (Exception ex) {
688  String msg = "Unable to parse fragment";
689  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
690  if(skipNContinue){
691  // Add revers modification of failed modification to exception
692  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
693  continue;
694  } else {
695  docUpdateEx.setErrorMessage("Unable to parse fragment");
696  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
697  // Remove last generated reverse modifications as, these modifications are not saved to document
698  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
699  failed = true;
700  break;
701  }
702  }
703 
704  // Import and insertion of node
705  Node insertNode = doc.importNode(fragment, true);
706  sourceParentNode.insertBefore(insertNode, sourceNode);
707 
708  //Merge text nodes
709  sourceParentNode.normalize();
710  } else if (tm.getMode() == Constants.TEXT_MOD_INS_AFTER) {
711  /*
712  * InsertAfter modification
713  */
714 
715  Node sourceParentNode = sourceNode.getParentNode();
716  if (sourceParentNode == null) {
717  /*
718  * Element does not have its parent - insertion aborded Choose whether
719  * skip&continue or interrupt modification process
720  */
721  if (skipNContinue) {
722  // Add revers modification of failed modification to exception
723  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
724  continue;
725  } else {
726  docUpdateEx.setErrorMessage("Unable to search for a parent node");
727  docUpdateEx.setErrorCode(Localisation.ERROR_33_MODULE_ERROR);
728  // Remove last generated reverse modifications as, these modifications are not saved to document
729  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
730  failed = true;
731  break;
732  }
733  }
734 
735  // Create document fragment from content of requested modification
736  DocumentFragment fragment;
737  try {
738  fragment = matcherProvider.getFragmentFromString(content);
739  } catch (Exception ex) {
740  String msg = "Unable to parse fragment";
741  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
742  if (skipNContinue) {
743  // Add revers modification of failed modification to exception
744  docUpdateEx.addReverseModifications(new ArrayList<TextModification>(reverseModifications.subList(0, createdReversModsNum)));
745  continue;
746  } else {
747  docUpdateEx.setErrorMessage("Unable to parse fragment");
748  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
749  // Remove last generated reverse modifications as, these modifications are not saved to document
750  DocumentUpdateHelper.removeListElements(reverseModifications, 0, createdReversModsNum);
751  failed = true;
752  break;
753  }
754  }
755 
756  // Import node
757  Node insertNode = doc.importNode(fragment, true);
758 
759  // Check, whether source node is last child
760  if (sourceNode.getNextSibling() == null) {
761  // Needed to append node at the end
762  sourceParentNode.appendChild(insertNode);
763  } else { // There is at least one more child after this node
764  // Insert node before the source's node next sibling
765  sourceParentNode.insertBefore(insertNode, sourceNode.getNextSibling());
766  }
767 
768  //Merge text nodes
769  sourceParentNode.normalize();
770  }
771  } // while (modsIt.hasNext())
772 
773  String tmp = DocumentUpdateHelper.nodeToString(doc.getDocumentElement());
774  setDocumentString(tmp);
775 
776  if (failed && !reverseRun) {
777  updateDocument(true, true);
778  throw docUpdateEx;
779  }
780  } // updateDocument()
781 
782  /**
783  * Method creates reversed modifications to document, that may be applied onto it in case of modification failure
784  * It inserts created modification into array list passed as parameter
785  *
786  * @param reversedAL List of reversed modifications
787  * @param modification Modification on which will be created reverse modification
788  * @param sourceNode Node used in modification
789  *
790  * @return Number of created modifications
791  *
792  */
793  private Integer generateReverseModifications(ArrayList<TextModification> reversedAL, TextModification modification, Node sourceNode){
794 
795  // Create temporary document fragment
796  DocumentFragment tmpFrag = null;
797 
798  if (modification.getNewContent() != null) {
799  try {
800  tmpFrag = matcherProvider.getFragmentFromString(modification.getNewContent());
801  } catch (Exception ex) {
802  String msg = "Unable to parse fragment";
803  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
804  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
805  return null;
806  }
807  }
808 
809  String mainXPath = modification.getPath();
810  int modificationNumber = 0;
811 
812  if (modification.getMode() == Constants.TEXT_MOD_INS_BEFORE) {
813  /*
814  * Reverse to insertBefore
815  */
816 
817  Node child = tmpFrag.getFirstChild();
818  int counter = -1;
819 
820  /*
821  * Process all fragment childs 'While loop' uses counter to correctly sort
822  * reversed modifications, first has to be removed non text elements, and
823  * once they will be removed, text ones can be edited
824  */
825  while (child != null) {
826  //Increase counter of created modifications
827  modificationNumber++;
828  counter++;
829 
830  if (sourceNode.getNodeType() != Node.TEXT_NODE) { // If source node is not text node
831  if (child.getNodeType() != Node.TEXT_NODE) {
832  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_BEFORE);
833  TextModification tmpMod = new TextModification(xPath, null, null, null);
834  reversedAL.add(0, tmpMod);
835  } /*
836  * As source node is not text node, it wont be merged together with
837  * inserted node (in case that inserted node is text type node)
838  * However, there is still posibility that inserted node will be
839  * merged with source's previous sibling node
840  */ else {
841  if (sourceNode.getPreviousSibling() != null) {
842  if (sourceNode.getPreviousSibling().getNodeType() != Node.TEXT_NODE) {
843  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_BEFORE);
844  TextModification tmpMod = new TextModification(xPath, null, null, null);
845  reversedAL.add(0, tmpMod);
846  } else {
847  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
848  TextModification tmpMod = new TextModification(xPath, sourceNode.getPreviousSibling().getTextContent().length(), child.getTextContent().length(), "");
849  reversedAL.add(counter, tmpMod);
850  }
851  }
852  }
853 
854  } else { // If source node is text node
855  // If node's type is not text, then we need to create reverse modification which will remove whole inserted element
856  if(child.getNodeType() != Node.TEXT_NODE){
857  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_BEFORE);
858  TextModification tmpMod = new TextModification(xPath, null, null, null);
859  reversedAL.add(0, tmpMod);
860  } else { // If node's type is text, we need to create reverse modification which will remove just inserted text
861  TextModification tmpMod = new TextModification(mainXPath, 0, child.getTextContent().length(), "");
862  reversedAL.add(counter, tmpMod);
863  }
864  }
865 
866  //Move to next child
867  child = child.getNextSibling();
868  }
869 
870  } else if (modification.getMode() == Constants.TEXT_MOD_INS_AFTER) {
871  /*
872  * Reverse to insertAfter
873  */
874 
875  Node child = tmpFrag.getFirstChild();
876  int counter = -1;
877 
878  /*
879  * Process all fragments childs 'While loop' uses counter to correctly
880  * sort reversed modifications, first has to be removed non text elements,
881  * and once they will be removed, text ones can be edited
882  */
883  while (child != null) {
884  // Increase counter of created modifications
885  modificationNumber++;
886 
887  counter++;
888 
889  if (sourceNode.getNodeType() != Node.TEXT_NODE) { // If source node is not text node
890  if (child.getNodeType() != Node.TEXT_NODE) {
891  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_AFTER);
892  TextModification tmpMod = new TextModification(xPath, null, null, null);
893  reversedAL.add(0, tmpMod);
894  } else {
895  /*
896  * As source node is not text node, it wont be merged together with
897  * inserted node (in case that inserted node is text type node)
898  * However, there is still posibility that inserted node will be
899  * merged with source's next sibling node
900  */
901 
902  if (sourceNode.getNextSibling() != null) {
903  if (sourceNode.getNextSibling().getNodeType() != Node.TEXT_NODE) {
904  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_AFTER);
905  TextModification tmpMod = new TextModification(xPath, null, null, null);
906  reversedAL.add(0, tmpMod);
907  } else {
908  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
909  TextModification tmpMod = new TextModification(xPath, 0, child.getTextContent().length(), "");
910  reversedAL.add(counter, tmpMod);
911  }
912  }
913  }
914 
915  } else { // If source node is text node
916  // If node's type is not text, then we need to create reverse modification which will remove whole inserted element
917  if(child.getNodeType() != Node.TEXT_NODE){
918  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_INS_AFTER);
919  TextModification tmpMod = new TextModification(xPath, null, null, null);
920  reversedAL.add(0, tmpMod);
921  } else { // If node's type is text, we need to create reverse modification which will remove just inserted text
922  TextModification tmpMod = new TextModification(mainXPath, sourceNode.getTextContent().length(), child.getTextContent().length(), "");
923  reversedAL.add(counter, tmpMod);
924  }
925  }
926 
927  // Move to next child of fragment
928  child = child.getNextSibling();
929  } // while (child != null)
930 
931  } else if (modification.getMode() == Constants.TEXT_MOD_REPLACE) {
932  /*
933  * Reverse to replace
934  */
935 
936  String content = modification.getNewContent();
937  Integer offset = modification.getOffset();
938  Integer length = modification.getLength();
939 
940  /*
941  * Modification will remove whole node, so we need to create modification,
942  * which will insert that node back to document
943  */
944  if (offset == null && length == null && content == null) {
945  /*
946  * If modification will remove whole node, the situation may arise where
947  * node does not have any siblings so his parent will be automatically
948  * deleted as well Therefore we need to search in "upper level" for a
949  * parent node, who has at least one sibling, however maximum level is
950  * at 'body' element
951  */
952  if (sourceNode.getChildNodes().getLength() < 2) {
953 
954  Node tmpNode = null;
955  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
956  tmpNode = sourceNode;
957  sourceNode = sourceNode.getParentNode();
958  }
959 
960  sourceNode = tmpNode;
961  }
962 
963  /*
964  * Special case, when body after removal will remain empty without any
965  * childs Reverse modificiation to this deletion has to be replacement
966  * of whole body node
967  */
968  if (sourceNode.getParentNode().getNodeName().equalsIgnoreCase("body")
969  && sourceNode.getPreviousSibling() == null && sourceNode.getNextSibling() == null) {
970 
971  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode.getParentNode());
972  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getParentNode());
973 
974  TextModification tmpMod = new TextModification(xPath, null, null, oldContent);
975  reversedAL.add(0, tmpMod);
976 
977  //Increase counter of created modifications
978  modificationNumber++;
979  } else if (sourceNode.getPreviousSibling() != null && sourceNode.getNextSibling() != null
980  && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE
981  && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE) {
982  /*
983  * Once we have founded a node with sibling, generating of
984  * modification can continue However, a situation may occur, when node
985  * which has to be removed has previous and next sibling both of
986  * text-node type After removal, those two siblings will merget
987  * together, so after that, we will need to add old node back to his
988  * position
989  */
990 
991  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
992  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
993  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
994 
995  TextModification tmpMod = new TextModification(xPath, newOffset, null, oldContent);
996  reversedAL.add(0, tmpMod);
997 
998  //Increase counter of created modifications
999  modificationNumber++;
1000  } else if (sourceNode.getPreviousSibling() != null) {
1001  // If node has previous sibling, we can create insertAfter modification
1002 
1003  //Increase counter of created modifications
1004  modificationNumber++;
1005 
1006  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1007  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1008  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_AFTER);
1009  reversedAL.add(0, tmpMod);
1010  } else if (sourceNode.getNextSibling() != null) {
1011  // In case of reversed InsertBefore we have to calculate xpath, due to changes that affect document after modification
1012  String xPath = "";
1013 
1014  //Increase counter of created modifications
1015  modificationNumber++;
1016 
1017  if (sourceNode.getNodeType() != Node.TEXT_NODE) {
1018  if (((Element) sourceNode).getTagName().equals(((Element) sourceNode.getNextSibling()).getTagName())) {
1019  xPath = DocumentUpdateHelper.getNodePathForReversed(XPathHelper.XPathStringOfNode(sourceNode.getNextSibling()), sourceNode,
1020  ((Element) sourceNode).getTagName(), Constants.TEXT_MOD_INS_BEFORE);
1021  }
1022  } else {
1023  xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1024  }
1025 
1026  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1027  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_BEFORE);
1028  reversedAL.add(0, tmpMod);
1029  }
1030 
1031  } else if (offset == null && length == null && content != null) {
1032  /*
1033  * Modification will replace whole node, so we need to create
1034  * modification, which will insert that node back to document
1035  */
1036 
1037  // Create fragment
1038  DocumentFragment frag = null;
1039  try {
1040  frag = matcherProvider.getFragmentFromString(content);
1041  } catch (Exception ex) {
1042  String msg = "Unable to parse fragment";
1043  Logger.getLogger(DocumentUpdate.class.getName()).log(Level.SEVERE, null, msg);
1044  docUpdateEx.setErrorCode(Localisation.ERROR_105_MODIFICATION_SPECIFICATION);
1045  return null;
1046  }
1047 
1048  /*
1049  * If content will be empty string, we need to do the same operations as
1050  * is done in deletion section Find a node which has at least one
1051  * sibling, however maximum depth can be "body" element
1052  */
1053  /*
1054  * FRAGMENT CONTENT IS EMPTY
1055  */
1056  if (frag.getTextContent().isEmpty()) {
1057  /*
1058  * If modification will remove whole node, the situation may arise
1059  * where node does not have any siblings so his parent will be
1060  * automatically deleted as well Therefore we need to search in "upper
1061  * level" for a parent node, who has at least one sibling, however
1062  * maximum level is at 'body' element
1063  */
1064  if (sourceNode.getChildNodes().getLength() < 2) {
1065 
1066  Node tmpNode = null;
1067  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
1068  tmpNode = sourceNode;
1069  sourceNode = sourceNode.getParentNode();
1070  }
1071 
1072  sourceNode = tmpNode;
1073  }
1074 
1075  /*
1076  * This is a special case, when body after removal will remain empty
1077  * without any childs Reverse modificiation to this deletion has to be
1078  * replacement of whole body node
1079  */
1080  if (sourceNode.getParentNode().getNodeName().equalsIgnoreCase("body")
1081  && sourceNode.getPreviousSibling() == null && sourceNode.getNextSibling() == null) {
1082 
1083  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode.getParentNode());
1084  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getParentNode());
1085 
1086  TextModification tmpMod = new TextModification(xPath, null, null, oldContent);
1087  reversedAL.add(0, tmpMod);
1088 
1089  //Increase counter of created modifications
1090  modificationNumber++;
1091  } else if (sourceNode.getPreviousSibling() != null && sourceNode.getNextSibling() != null
1092  && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE
1093  && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE){
1094  /*
1095  * Once we have founded a node with sibling, generating of
1096  * modification can continue However, a situation may occur, when
1097  * node which has to be removed has previous and next sibling both
1098  * of text-node type After removal, those two siblings will merget
1099  * together, so after that, we will need to add old node back to his
1100  * position
1101  */
1102  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1103  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1104  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1105 
1106  TextModification tmpMod = new TextModification(xPath, newOffset, null, oldContent);
1107  reversedAL.add(0, tmpMod);
1108 
1109  //Increase counter of created modifications
1110  modificationNumber++;
1111  } else if (sourceNode.getPreviousSibling() != null) {
1112  // If node has previous sibling, we can create insertAfter modification
1113 
1114  // Increase counter of created modifications
1115  modificationNumber++;
1116 
1117  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1118  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1119  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_AFTER);
1120  reversedAL.add(0, tmpMod);
1121  } else if (sourceNode.getNextSibling() != null) {
1122  // In case of reversed InsertBefore we have to calculate xpath, due to changes that affect document after modification
1123 
1124  String xPath = "";
1125 
1126  //Increase counter of created modifications
1127  modificationNumber++;
1128 
1129  if (sourceNode.getNodeType() != Node.TEXT_NODE) {
1130  if (((Element) sourceNode).getTagName().equals(((Element) sourceNode.getNextSibling()).getTagName())) {
1131  xPath = DocumentUpdateHelper.getNodePathForReversed(XPathHelper.XPathStringOfNode(sourceNode.getNextSibling()), sourceNode,
1132  ((Element) sourceNode).getTagName(), Constants.TEXT_MOD_INS_BEFORE);
1133  }
1134  } else {
1135  xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1136  }
1137 
1138  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1139  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_BEFORE);
1140  reversedAL.add(0, tmpMod);
1141  }
1142  } else {
1143  /*
1144  * FRAGMENT CONTENT IS NOT EMPTY
1145  */
1146 
1147  Node child = frag.getLastChild();
1148 
1149  if (frag.getChildNodes().getLength() == 1) {
1150  /*
1151  * If fragment has exactly one child
1152  */
1153 
1154  if (child.getNodeType() == Node.TEXT_NODE) {
1155  /*
1156  * If fragment has only one child and its type is TEXT, we need to
1157  * check whether siblings of source(replacement) node are not TEXT
1158  * nodes as well, due to merging of text nodes system
1159  */
1160 
1161  if (sourceNode.getPreviousSibling() != null && sourceNode.getNextSibling() != null
1162  && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE
1163  && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE) {
1164 
1165  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1166  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1167  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1168 
1169  /*
1170  * First create modification, that will remove the inserted
1171  * text, then make modification, that will insert between those
1172  * two text nodes, original node
1173  */
1174  TextModification tmpMod = new TextModification(xPath, newOffset, null, oldContent, Constants.TEXT_MOD_REPLACE);
1175  reversedAL.add(0, tmpMod);
1176 
1177  TextModification tmpMod2 = new TextModification(xPath, newOffset, child.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1178  reversedAL.add(0, tmpMod2);
1179 
1180  //Increase counter of created modifications
1181  modificationNumber++;
1182  } else if (sourceNode.getPreviousSibling() != null && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE){
1183  // If source's node previous sibling is textNode
1184 
1185  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1186  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1187 
1188  TextModification tmpMod = new TextModification(xPath, newOffset, child.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1189  reversedAL.add(0, tmpMod);
1190 
1191  modificationNumber++;
1192  } else if (sourceNode.getNextSibling() != null && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE) {
1193  // If source's node previous sibling is textNode
1194  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1195 
1196  TextModification tmpMod = new TextModification(xPath, 0, child.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1197  reversedAL.add(0, tmpMod);
1198 
1199  modificationNumber++;
1200  } else {
1201  // Simply replace replaced fragment
1202  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1203  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1204 
1205  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1206  reversedAL.add(0, tmpMod);
1207  modificationNumber++;
1208  }
1209  } else {
1210  // Simply replace replaced fragment
1211 
1212  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1213  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1214 
1215  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1216  reversedAL.add(0, tmpMod);
1217  modificationNumber++;
1218  }
1219  } else if (frag.getChildNodes().getLength() == 2) {
1220  /*
1221  * If fragment has exactly two childs
1222  */
1223 
1224  Node child1 = frag.getFirstChild();
1225  Node child2 = frag.getLastChild();
1226  if (frag.getFirstChild().getNodeType() == Node.TEXT_NODE) {
1227  // If first child is text node
1228 
1229  // Check whether source previous sibling is text node
1230  if (sourceNode.getPreviousSibling() != null && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
1231  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1232  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1233 
1234  TextModification tmpMod = new TextModification(xPath, newOffset, child1.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1235  reversedAL.add(0, tmpMod);
1236 
1237  modificationNumber++;
1238  } else { // Remove first inserted child
1239  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child1.getNodeName(), Constants.TEXT_MOD_REPLACE);
1240  TextModification tmpMod = new TextModification(xPath, null, null, null, Constants.TEXT_MOD_REPLACE);
1241  reversedAL.add(0, tmpMod);
1242  modificationNumber++;
1243  }
1244  // Replace seconds inserted child node with original content
1245  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1246  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child2.getNodeName(), Constants.TEXT_MOD_REPLACE);
1247 
1248  // NOTE: INSERTING MODIFICATION AT 2ND PLACE IN LIST, BECAUSE FIRST HAS TO BE DELETED FIRST CHILD
1249  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1250  reversedAL.add(1, tmpMod);
1251  modificationNumber++;
1252  } else if (frag.getLastChild().getNodeType() == Node.TEXT_NODE) {
1253  // If second(last) child is text node
1254 
1255  // Check whether source next sibling is text node
1256  if(sourceNode.getNextSibling() != null && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE){
1257  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1258 
1259  TextModification tmpMod = new TextModification(xPath, 0, child.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1260  reversedAL.add(0, tmpMod);
1261 
1262  modificationNumber++;
1263  }
1264  // Remove second inserted child
1265  else{
1266  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child2.getNodeName(), Constants.TEXT_MOD_REPLACE);
1267  TextModification tmpMod = new TextModification(xPath, null, null, null, Constants.TEXT_MOD_REPLACE);
1268 
1269  reversedAL.add(0, tmpMod);
1270 
1271  modificationNumber++;
1272  }
1273 
1274  // Replace first inserted child node with original content
1275  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1276  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child1.getNodeName(), Constants.TEXT_MOD_REPLACE);
1277  // NOTE: INSERTING MODIFICATION AT 2ND PLACE IN LIST, BECAUSE FIRST HAS TO BE DELETED FIRST CHILD
1278  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1279 
1280  reversedAL.add(1, tmpMod);
1281  modificationNumber++;
1282  } else {
1283  // Simple remove one child and next one will be replaced back to document's original content
1284 
1285  String xPath1 = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child1.getNodeName(), Constants.TEXT_MOD_REPLACE);
1286  String xPath2 = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child2.getNodeName(), Constants.TEXT_MOD_REPLACE);
1287  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1288 
1289  TextModification tmpMod = new TextModification(xPath1, null, null, null, Constants.TEXT_MOD_REPLACE);
1290  TextModification tmpMod2 = new TextModification(xPath2, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1291 
1292  reversedAL.add(0, tmpMod2);
1293  reversedAL.add(0, tmpMod);
1294 
1295  modificationNumber++;
1296  modificationNumber++;
1297  }
1298 
1299  } else {
1300  /*
1301  * If fragment has three or more childs
1302  */
1303 
1304  ArrayList<TextModification> stack = new ArrayList<TextModification>();
1305  /*
1306  * If fragment has at least two childs:
1307  * If child is first one and its type is text node we need to check whether previous source(replacement) node sibling is not
1308  * text node either - they will be merged together
1309  */
1310  while (child != null) {
1311  /*
1312  * If child is first one and it's type is text node, and souce's
1313  * previous sibling's type is text node as well we need to remove
1314  * part of inserted text, as those two elements will be
1315  * automatically merged together
1316  */
1317 
1318  if (child.getPreviousSibling() == null && sourceNode.getPreviousSibling() != null
1319  && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE && child.getNodeType() == Node.TEXT_NODE) {
1320 
1321  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1322  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1323  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1324 
1325  TextModification tmpMod = new TextModification(xPath, newOffset, child.getTextContent().length(), "", Constants.TEXT_MOD_REPLACE);
1326  reversedAL.add(0, tmpMod);
1327  } else if (child.getNextSibling() != null && child.getNextSibling().getNextSibling() == null) {
1328  /*
1329  * If child is penultimate, here comes replacement process
1330  */
1331 
1332  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1333  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1334 
1335  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1336  reversedAL.add(0, tmpMod);
1337  } else if (child.getNextSibling() == null && sourceNode.getNextSibling() != null
1338  && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE && child.getNodeType() == Node.TEXT_NODE) {
1339  /*
1340  * If type of last child is text, and next sibling of
1341  * source(replace) node is text type as well we need to remove
1342  * part of inserted text, as those two elements will be
1343  * automatically merged together
1344  */
1345 
1346  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1347  int newLength = child.getTextContent().length();
1348 
1349  TextModification tmpMod = new TextModification(xPath, 0, newLength, "", Constants.TEXT_MOD_REPLACE);
1350  reversedAL.add(0, tmpMod);
1351  } else if (child.getNextSibling() == null) {
1352  /*
1353  * If child is last one and his type is not Text
1354  */
1355 
1356  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1357  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1358 
1359  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_REPLACE);
1360  reversedAL.add(0, tmpMod);
1361  } else {
1362  /*
1363  * All elements between first and last will be simply deleted
1364  */
1365 
1366  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1367 
1368  TextModification tmpMod = new TextModification(xPath, null, null, null, Constants.TEXT_MOD_REPLACE);
1369  if(child.getNodeType() == Node.TEXT_NODE){
1370  stack.add(0, tmpMod);
1371  } else {
1372  reversedAL.add(0, tmpMod);
1373  }
1374  }
1375  // Increase counter of created modifications
1376  modificationNumber++;
1377 
1378  child = child.getPreviousSibling();
1379 
1380  }
1381 
1382  reversedAL.addAll(0, stack);
1383  }
1384  }
1385  } else if (offset != null && length == null && content != null) {
1386  /*
1387  * Modification will make insertion new content into node, so all
1388  * inserted content need to be removed NOTE: Can be applied only on text
1389  * node
1390  */
1391 
1392  Node child = tmpFrag.getFirstChild();
1393  int counter = -1;
1394 
1395  while (child != null) {
1396  // Increase counter of created modifications
1397  modificationNumber++;
1398 
1399  counter++;
1400  if (child.getNodeType() != Node.TEXT_NODE) {
1401  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1402  TextModification tmpMod = new TextModification(xPath, null, null, null);
1403  reversedAL.add(0, tmpMod);
1404  } else {
1405  TextModification tmpMod = new TextModification(mainXPath, offset, child.getTextContent().length(), "");
1406  reversedAL.add(counter, tmpMod);
1407  }
1408  //Move to next child of fragment
1409  child = child.getNextSibling();
1410  }
1411 
1412  } else if (offset != null && length != null && content != null) {
1413  /*
1414  * Modification will replace of part of node, so it's needed to remove
1415  * all replaced nodes and insert back part of old one NOTE: Can be
1416  * applied only on text node
1417  */
1418 
1419  Node child = tmpFrag.getFirstChild();
1420  int counter = -1;
1421 
1422  /*
1423  * If content, that is going to take a place is empty string and it's
1424  * offset is set to 0 and length has the same length as source's node
1425  * length, will cause, that node will remain empty -> it has to be
1426  * removed then
1427  *
1428  * NOTE: This section should be consulted with supervisor if it's even
1429  * possible to achieve this state when content will be empty string If
1430  * there is no chance to get into this state, whole 'if' block need to
1431  * be removed
1432  */
1433  if (content.isEmpty() && offset == 0 && length == sourceNode.getTextContent().length()) {
1434  /*
1435  * If modification will remove whole node, the situation may arise
1436  * where node does not have any siblings so his parent will be
1437  * automatically deleted as well Therefore we need to search in "upper
1438  * level" for a parent node, who has at least one sibling, however
1439  * maximum level is at 'body' element
1440  */
1441  if (sourceNode.getChildNodes().getLength() < 2) {
1442 
1443  Node tmpNode = null;
1444  while (sourceNode.getChildNodes().getLength() < 2 && !sourceNode.getNodeName().equalsIgnoreCase("body")) {
1445  tmpNode = sourceNode;
1446  sourceNode = sourceNode.getParentNode();
1447  }
1448 
1449  sourceNode = tmpNode;
1450  }
1451 
1452  /*
1453  * Special case, when body after removal will remain empty without any
1454  * childs Reverse modificiation to this deletion has to be replacement
1455  * of whole body node
1456  */
1457  if (sourceNode.getParentNode().getNodeName().equalsIgnoreCase("body")
1458  && sourceNode.getPreviousSibling() == null && sourceNode.getNextSibling() == null) {
1459 
1460  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode.getParentNode());
1461  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getParentNode());
1462 
1463  TextModification tmpMod = new TextModification(xPath, null, null, oldContent);
1464  reversedAL.add(0, tmpMod);
1465 
1466  //Increase counter of created modifications
1467  modificationNumber++;
1468  } else if (sourceNode.getPreviousSibling() != null && sourceNode.getNextSibling() != null
1469  && sourceNode.getPreviousSibling().getNodeType() == Node.TEXT_NODE
1470  && sourceNode.getNextSibling().getNodeType() == Node.TEXT_NODE) {
1471  /*
1472  * Once we have founded a node with sibling, generating of
1473  * modification can continue However, a situation may occur, when
1474  * node which has to be removed has previous and next sibling both
1475  * of text-node type After removal, those two siblings will merget
1476  * together, so after that, we will need to add old node back to his
1477  * position
1478  */
1479 
1480  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1481  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1482  int newOffset = sourceNode.getPreviousSibling().getTextContent().length();
1483 
1484  TextModification tmpMod = new TextModification(xPath, newOffset, null, oldContent);
1485  reversedAL.add(0, tmpMod);
1486 
1487  // Increase counter of created modifications
1488  modificationNumber++;
1489  } else if (sourceNode.getPreviousSibling() != null) {
1490  // If node has previous sibling, we can create insertAfter modification
1491 
1492  // Increase counter of created modifications
1493  modificationNumber++;
1494 
1495  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1496  String xPath = XPathHelper.XPathStringOfNode(sourceNode.getPreviousSibling());
1497  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_AFTER);
1498  reversedAL.add(0, tmpMod);
1499  } else if (sourceNode.getNextSibling() != null) {
1500  // In case of reversed InsertBefore we have to calculate xpath, due to changes that affect document after modification
1501 
1502  String xPath = "";
1503 
1504  //Increase counter of created modifications
1505  modificationNumber++;
1506 
1507  if (sourceNode.getNodeType() != Node.TEXT_NODE) {
1508  if (((Element) sourceNode).getTagName().equals(((Element) sourceNode.getNextSibling()).getTagName())) {
1509  xPath = DocumentUpdateHelper.getNodePathForReversed(XPathHelper.XPathStringOfNode(sourceNode.getNextSibling()), sourceNode,
1510  ((Element) sourceNode).getTagName(), Constants.TEXT_MOD_INS_BEFORE);
1511  }
1512  } else {
1513  xPath = XPathHelper.XPathStringOfNode(sourceNode.getNextSibling());
1514  }
1515 
1516  String oldContent = DocumentUpdateHelper.nodeToString(sourceNode);
1517  TextModification tmpMod = new TextModification(xPath, null, null, oldContent, Constants.TEXT_MOD_INS_BEFORE);
1518  reversedAL.add(0, tmpMod);
1519  }
1520  } else {
1521  // Get content which will be overwritten by modification
1522  String overContent = sourceNode.getTextContent().substring(offset, offset + length);
1523 
1524  while (child != null) {
1525  //Increase counter of created modifications
1526  modificationNumber++;
1527  counter++;
1528 
1529 
1530  if (child.getNodeType() != Node.TEXT_NODE) {
1531  String xPath = DocumentUpdateHelper.getNodePathForReversed(mainXPath, sourceNode, child.getNodeName(), Constants.TEXT_MOD_REPLACE);
1532  TextModification tmpMod = new TextModification(xPath, null, null, null);
1533  reversedAL.add(0, tmpMod);
1534  } else {
1535  TextModification tmpMod = new TextModification(mainXPath, offset, child.getTextContent().length(), "");
1536  reversedAL.add(counter, tmpMod);
1537  }
1538  // Move to next child of fragment
1539  child = child.getNextSibling();
1540  }
1541  counter++;
1542 
1543  // Last reverse modification has to be insertion of original content back to its node
1544  TextModification tmpMod = new TextModification(mainXPath, offset, null, overContent);
1545  reversedAL.add(counter, tmpMod);
1546  }
1547  } // Modification will replace of part of node ...
1548  } // modification.getMode() == Constants.TEXT_MOD_REPLACE
1549  return modificationNumber;
1550  } // generateReverseModifications()
1551 } // public class DocumentUpdate
void removeModification(TextModification modification)
Class providing access to available matchers.
Integer generateReverseModifications(ArrayList< TextModification > reversedAL, TextModification modification, Node sourceNode)
void updateDocument(boolean failed, boolean reverseRun, ArrayList< TextModification > modifications)
Exception of DocumentUpdate class which is thrown in case of modification failure.
Class representing modification of annotated document text.
void appendModifications(ArrayList< TextModification > modifications)
void appendModification(TextModification modification)
void updateDocument(boolean failed, boolean reverseRun)
Class responsible for localised strings.
Informations about client session.
DocumentUpdate(String documentString, EditorSession edSession)