4A Server -  2.0
 All Classes Namespaces Files Functions Variables Enumerator
TextModificationsXMLUnitProvider.java
Go to the documentation of this file.
1 /*
2  * Project: Server for annotations sharing
3  * Author: Ing. Lukáš Kubík kubiklukas@seznam.cz
4  * File: TextModificationsXMLUnitProvider.java
5  * Description: Class for providing TextModification differences between two versions of document
6  */
7 
8 /**
9  * @file TextModificationsXMLUnitProvider.java
10  *
11  * @brief TextModification provider
12  */
13 
14 /**
15  * @package cz.vutbr.fit.knot.annotations.textModifications
16  *
17  * @brief Classes for providing TextModification differences between two versions of document
18  */
19 package cz.vutbr.fit.knot.annotations.textModifications;
20 
24 import java.io.IOException;
25 import java.io.StringReader;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map.Entry;
31 import java.util.Set;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.xpath.XPathExpressionException;
34 import nu.validator.htmlparser.dom.HtmlDocumentBuilder;
35 import org.custommonkey.xmlunit.DetailedDiff;
36 import org.custommonkey.xmlunit.Diff;
37 import org.custommonkey.xmlunit.Difference;
38 import org.custommonkey.xmlunit.NodeDetail;
39 import org.custommonkey.xmlunit.XMLUnit;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.NamedNodeMap;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44 import org.xml.sax.InputSource;
45 import org.xml.sax.SAXException;
46 
47 /**
48  * Class for providing TextModification differences between two versions of document
49  *
50  * @brief TextModification provider
51  * @author xkubik22
52  */
54 
55 
56  /**
57  * Provides the TextModifications
58  *
59  * @param oldVersion Old version of the document
60  * @param newVersion New version of the document
61  * @param highestProtocol Highest available protocol version
62  * @return New TextModifications
63  * @throws ParserConfigurationException
64  * @throws SAXException
65  * @throws IOException
66  */
67  public static ArrayList<TextModification> getTextModifications(String oldVersion, String newVersion,
68  int highestProtocol) throws ParserConfigurationException, SAXException, IOException{
69  //parse strings into the Document entities
70  HtmlDocumentBuilder htmlDocumentBuilder = new HtmlDocumentBuilder();
71 
72  InputSource inputSourceOld = new InputSource(new StringReader(oldVersion));
73  InputSource inputSourceNew = new InputSource(new StringReader(newVersion));
74 
75  Document oldDom = htmlDocumentBuilder.parse(inputSourceOld);
76  Document newDom = htmlDocumentBuilder.parse(inputSourceNew);
77 
78  //call the real text modification provider
79  return getTextModifications(oldDom, newDom, highestProtocol);
80  }
81 
82  /**
83  * Provides the TextModifications
84  *
85  * @param oldVersion Old version of the document
86  * @param newVersion New version of the document
87  * @param highestProtocol Highest available protocol version
88  * @return New TextModifications
89  * @throws SAXException
90  * @throws IOException
91  */
92  public static ArrayList<TextModification> getTextModifications(Document oldVersion, Document newVersion,
93  int highestProtocol) throws SAXException, IOException{
94  //This configuration will ignore attribute order!
95  XMLUnit.setIgnoreAttributeOrder(false);
96  //diff documents
97  Diff diff = new Diff(oldVersion, newVersion);
98  DetailedDiff myDiff = new DetailedDiff(diff);
99  List allDifferences = myDiff.getAllDifferences();
100  // initialize variables
101  LinkedHashMap<String, TextModification> pathModMap = new LinkedHashMap<String, TextModification>();
102  // Failsafe variable - if an unprogrammed description or an unprogrammed behavior appears then whole body will be replaced
103  boolean unknownDif = false;
104  // loop throught the differences
105  for (int i=0; i<allDifferences.size(); i++) {
106  Difference difference = (Difference) allDifferences.get(i);
107  // old document node detail
108  NodeDetail controlNode = difference.getControlNodeDetail();
109  // difference description
110  String desc = difference.getDescription();
111  // new document node detail
112  NodeDetail testNode = difference.getTestNodeDetail();
113  // change a text of some node
114  if(desc.equalsIgnoreCase("text value")){
115  // only nodes with same xpaths are changed, otherwise it's some odd behavior of the library
116  if(controlNode.getXpathLocation() != null && controlNode.getXpathLocation().equals(testNode.getXpathLocation())){
117  replaceNode(pathModMap, testNode);
118  }
119  }
120  // add/remove a node
121  // Comparing 2 nodes and one holds more childnodes than can be
122  // matched against child nodes of the other.
123  else if(desc.equalsIgnoreCase("presence of child node")){
124  if(testNode.getXpathLocation() == null){
125  removeNode(pathModMap, controlNode, highestProtocol, newVersion);
126  continue;
127  }
128  Node failSafeNode = null;
129  try {
130  failSafeNode = XPathHelper.getNode(oldVersion, testNode.getXpathLocation());
131  } catch (XPathExpressionException ex) {
132  unknownDif = true;
133  break;
134  }
135  // if the node for add already exists in the old document then don't add - it's some odd behavior of the library
136  if(failSafeNode == null){
137  if(!addNode(highestProtocol, pathModMap, testNode, oldVersion)){
138  unknownDif = true;
139  }
140  } // failsafe
141  }
142  //add/remove a node
143  // Comparing 2 nodes with children whose nodes are in different sequence
144  else if(desc.equalsIgnoreCase("sequence of child nodes")){
145  if(controlNode.getXpathLocation() != null && controlNode.getXpathLocation().equals(testNode.getXpathLocation())){
146  replaceNode(pathModMap, testNode);
147  }
148  else{
149  if(controlNode.getXpathLocation() != null){
150  if(getNodeIfExists(newVersion, controlNode.getNode()) == null){
151  removeNode(pathModMap, controlNode, highestProtocol, newVersion);
152  continue; //only remove
153  }
154  else{
155  replaceNode(pathModMap, controlNode);
156  }
157  }
158  if(testNode.getXpathLocation() != null){
159  if(getNodeIfExists(oldVersion, testNode.getNode()) == null){
160  if(!addNode(highestProtocol, pathModMap, testNode, oldVersion)){
161  unknownDif = true;
162  break;
163  }
164  }
165  else{
166  replaceNode(pathModMap, testNode);
167  }
168  }
169  }
170  }
171  // redundant info
172  else if(desc.equalsIgnoreCase("number of child nodes")){
173  continue;
174  }
175  // replace element
176  else if(desc.equalsIgnoreCase("number of element attributes") ||
177  desc.equalsIgnoreCase("attribute name") ||
178  desc.equalsIgnoreCase("attribute value") ||
179  desc.equalsIgnoreCase("attribute value explicitly specified") ||
180  desc.equalsIgnoreCase("sequence of attributes") ||
181  desc.equalsIgnoreCase("element tag name") ||// Comparing 2 elements with different tag names
182  desc.equalsIgnoreCase("node type") ||/** Comparing 2 nodes with different node types */
183  desc.equalsIgnoreCase("presence of child nodes to be"))// Comparing 2 nodes but only one has any children
184  {
185  if(controlNode.getXpathLocation() != null && controlNode.getXpathLocation().equals(testNode.getXpathLocation())){
186  replaceNode(pathModMap, testNode);
187  }
188  else{
189  if(controlNode.getXpathLocation() != null){
190  if(getNodeIfExists(newVersion, controlNode.getNode()) == null){
191  removeNode(pathModMap, controlNode, highestProtocol, newVersion);
192  }
193  else{
194  replaceNode(pathModMap, controlNode);
195  }
196  }
197  if(testNode.getXpathLocation() != null){
198  if(getNodeIfExists(oldVersion, testNode.getNode()) == null){
199  if(!addNode(highestProtocol, pathModMap, testNode, oldVersion)){
200  unknownDif = true;
201  break;
202  }
203  }
204  else{
205  replaceNode(pathModMap, testNode);
206  }
207  }
208  }
209  }
210  // unknown type of the difference
211  else{
212  unknownDif = true;
213  break;
214  }
215  }
216  // unknown type of difference appeared in the update -> update whole body
217  if(unknownDif){
218  ArrayList<TextModification> tmsUnit = new ArrayList<TextModification>();
219  tmsUnit.add(exchangeWholeBody(newVersion));
220  return tmsUnit;
221  }
222  ArrayList<TextModification> tmsUnit = new ArrayList<TextModification>(pathModMap.values());
223  return tmsUnit;
224  }
225 
226  /**
227  * Returns a text modification on whole body element
228  *
229  * @param newVersion New version of the document
230  * @return text modification on whole body element
231  */
232  private static TextModification exchangeWholeBody(Document newVersion){
233  Node node = newVersion.getFirstChild();
234  while (node == null || !node.getNodeName().equalsIgnoreCase("html")) {
235  return null;
236  }
237  String replace = "";
238  replace += nodeToString(node);
239  return new TextModification("/", null, null, replace);
240  }
241 
242  /**
243  * Converts node into string form
244  *
245  * @param root Node to convert
246  * @return Returns node in string form
247  *
248  */
249  public static String nodeToString(Node root) {
250 
251  String rootName = root.getNodeName().toLowerCase();
252  StringBuilder sb = new StringBuilder();
253 
254  if (root.getNodeType() != Node.TEXT_NODE) {
255  if (root.getNodeName().equalsIgnoreCase("br")) {
256  return "<br>";
257  } else if (root.getNodeName().equalsIgnoreCase("hr")) {
258  return "<hr>";
259  }
260 
261  sb.append("<").append(rootName);
262  sb.append(attributeToString(root.getAttributes()));
263  sb.append(">");
264 
265  NodeList nl = root.getChildNodes();
266  for (int i = 0; i < nl.getLength(); i++) {
267  Node n = nl.item(i);
268  if (n.getNodeType() == Node.TEXT_NODE) {
269  sb.append(n.getNodeValue());
270  } else {
271  sb.append(nodeToString(n));
272  }
273  }
274  sb.append("</").append(rootName).append(">");
275  } else {
276  sb.append(root.getNodeValue());
277  }
278 
279  return sb.toString();
280  }
281 
282  /**
283  * Converts attribute map into string
284  *
285  * @param attributes Attribute map for conversion
286  * @return Attribute converted to string
287  */
288  public static String attributeToString(NamedNodeMap attributes){
289  int attrLength = attributes.getLength();
290 
291  if(attrLength > 0){
292  StringBuilder sb = new StringBuilder(" ");
293  for(int i = 0; i < attrLength; i++){
294  Node attr = attributes.item(i);
295  sb.append(attr.getNodeName());
296  sb.append("=\"");
297  sb.append(attr.getNodeValue());
298  if(i != attrLength-1){
299  sb.append("\" ");
300  }
301  else{
302  sb.append("\"");
303  }
304  }
305  return sb.toString();
306  }
307 
308  return "";
309  }
310 
311  /**
312  * Method that gets the last sibling of the node
313  *
314  * @param node Node for sibling
315  * @return Last sibling
316  */
317  private static Node lastSiblingNode(Node node){
318  Node sibling = node;
319  while(sibling.getNextSibling() != null){
320  sibling = sibling.getNextSibling();
321  }
322  return sibling;
323  }
324 
325  /**
326  * Method that checks if there already exists a TextModification for
327  * some element or it's parent modification.
328  *
329  * @param pathModMap Map of the created text modifications
330  * @param XPath XPath of the element
331  * @return true if exists otherwise false
332  */
333  private static boolean existsParentMod(LinkedHashMap<String, TextModification> pathModMap, String XPath){
334  Set<String> paths = pathModMap.keySet();
335  Iterator<String> it = paths.iterator();
336  while(it.hasNext()){
337  String path = it.next();
338  if(XPath.contains(path)){
339  return true;
340  }
341  }
342  return false;
343  }
344 
345  /**
346  * Method that removes all child modifications of some element
347  *
348  * @param pathModMap Map of the created text modifications
349  * @param XPath XPath of the element
350  */
351  private static void removeAllChildMods(LinkedHashMap<String, TextModification> pathModMap, String XPath){
352  Iterator<Entry<String, TextModification>> setIt = pathModMap.entrySet().iterator();
353  while(setIt.hasNext()){
354  Entry<String, TextModification> entry = setIt.next();
355  if(entry.getKey().contains(XPath)){
356  setIt.remove();
357  }
358  }
359  }
360 
361  /**
362  * Method gets the first previous node of some node that exists in a version of the document
363  *
364  * @param testedNode Node fot getting it's previous node
365  * @param version Version of a document
366  * @return Previous node or null
367  */
368  private static Node getFirstPreviousNodeExistingInDoc(Node testedNode, Document version){
369  Node prevNode = testedNode.getPreviousSibling();
370  Node evaluatedNode = null;
371  while(prevNode != null && evaluatedNode == null){
372  try {
373  evaluatedNode = XPathHelper.getNode(version, XPathHelper.XPathStringOfNode(prevNode));
374  } catch (XPathExpressionException ex) {
375  prevNode = prevNode.getPreviousSibling();
376  continue;
377  }
378  prevNode = prevNode.getPreviousSibling();
379  }
380  return evaluatedNode;
381  }
382 
383  /**
384  * Method that test if some node is not a text node and not a br or a hr element
385  *
386  * @param testedNode Node for test
387  * @return True if node is a text node or a br or a hr element. Otherwise false.
388  */
389  private static boolean isNodeNotTextAndNotBRHR(Node testedNode){
390  if(testedNode.getNodeType() != Node.TEXT_NODE &&
391  !testedNode.getNodeName().equalsIgnoreCase("br") &&
392  !testedNode.getNodeName().equalsIgnoreCase("hr")){
393  return true;
394  }
395  return false;
396  }
397 
398  /**
399  * Gets the parent element in the oldVersion
400  *
401  * @param oldVersion Old version of the document
402  * @param child Child node for getting the parent node
403  * @return Parent node if exists or null
404  */
405  private static Node getParentIfExists(Document oldVersion, Node child){
406  Node parent = child.getParentNode();
407 
408  if(parent!= null){
409  String parentPath = XPathHelper.XPathStringOfNode(parent);
410  Node evaluatedNode = null;
411  try {
412  evaluatedNode = XPathHelper.getNode(oldVersion, parentPath);
413  } catch (XPathExpressionException ex) {
414  return null;
415  }
416  return evaluatedNode;
417  }
418 
419  return null;
420  }
421 
422  /**
423  * Gets the element in the version of the document
424  *
425  * @param version A Version of the document
426  * @param node node in the version
427  * @return Node if exists or null
428  */
429  private static Node getNodeIfExists(Document version, Node node){
430 
431  String parentPath = XPathHelper.XPathStringOfNode(node);
432  Node evaluatedNode = null;
433  try {
434  evaluatedNode = XPathHelper.getNode(version, parentPath);
435  } catch (XPathExpressionException ex) {
436  return null;
437  }
438  return evaluatedNode;
439  }
440 
441  /**
442  * Gets the element in the version of the document
443  *
444  * @param version A version of the document
445  * @param XPath XPath in the version
446  * @return Node if exists or null
447  */
448  private static Node getNodeIfExists(Document version, String XPath){
449  Node evaluatedNode = null;
450  try {
451  evaluatedNode = XPathHelper.getNode(version, XPath);
452  } catch (XPathExpressionException ex) {
453  return null;
454  }
455  return evaluatedNode;
456  }
457 
458  /**
459  * Creates a TextModification that adds a node
460  *
461  * @param highestProtocol highest available protocol version
462  * @param pathModMap Map of the created text modifications
463  * @param addNodeDetail Node for add
464  * @param oldVersion Old version of the document
465  * @return True if success, otherwise false
466  */
467  private static boolean addNode(int highestProtocol,
468  LinkedHashMap<String,TextModification> pathModMap,
469  NodeDetail addNodeDetail, Document oldVersion)
470  {
471  if(!existsParentMod(pathModMap, addNodeDetail.getXpathLocation())){
472  Node addNode = addNodeDetail.getNode();
473  boolean v2Success = false;
474  //not inserting text or br or hr element
475  boolean skipFirst = false;
476  if(highestProtocol >= Constants.PROTOCOL_LOD_V2){
477  skipFirst = true;
478  Node previousNode = addNode.getPreviousSibling();
479  boolean tryNextNode = false;
480  //try insert after
481  if(previousNode != null){
482  String path = XPathHelper.XPathStringOfNode(previousNode);
483  if(pathModMap.containsKey(path) || getNodeIfExists(oldVersion, previousNode) != null){
484  String content = nodeToString(addNode);
485  TextModification newMod = new TextModification(path, null, null, content, Constants.TEXT_MOD_INS_AFTER);
486  //Attention: adding the path where it is going to be added! Not the path of the previous node!
487  pathModMap.put(addNodeDetail.getXpathLocation(), newMod);
488  v2Success = true;
489  }
490  else{
491  tryNextNode = true;
492  }
493  }
494  else{
495  tryNextNode = true;
496  }
497  if(tryNextNode){
498  Node nextNode = addNode.getNextSibling();
499  //try insert before
500  if(nextNode != null){
501  String path = XPathHelper.XPathStringOfNode(nextNode);
502  if(pathModMap.containsKey(path) || getNodeIfExists(oldVersion, nextNode) != null){
503  String content = nodeToString(addNode);
504  TextModification newMod = new TextModification(path, null, null, content, Constants.TEXT_MOD_INS_BEFORE);
505  //Attention: adding the path where it is going to be added! Not the path of the next node!
506  pathModMap.put(addNodeDetail.getXpathLocation(), newMod);
507  v2Success = true;
508  }
509  }
510  }
511  }
512  //Old protocol - replace parent node or previous node
513  if(!v2Success){
514 
515  //inserting text element or br or hr element
516  if(!isNodeNotTextAndNotBRHR(addNode)){
517  skipFirst = true;
518  }
519  Node firstPreviousNode = null;
520  Node firstParentNode = null;
521  Node evaluatedNode = addNode;
522  while(firstPreviousNode == null && firstParentNode == null){
523  if(!skipFirst){
524  firstPreviousNode = getFirstPreviousNodeExistingInDoc(evaluatedNode, oldVersion);
525  }
526  skipFirst = false;
527  if(firstPreviousNode == null || !isNodeNotTextAndNotBRHR(firstPreviousNode)){
528  firstParentNode = getParentIfExists(oldVersion, evaluatedNode);
529  firstPreviousNode = null;
530  if(firstParentNode == null){
531  evaluatedNode = evaluatedNode.getParentNode();
532  if(evaluatedNode == null){
533  return false;
534  }
535  }
536  }
537  }
538  if(firstPreviousNode != null){
539  String firstPreviousNodePath = XPathHelper.XPathStringOfNode(firstPreviousNode);
540  //try get already existing replace modification for the first previous node
541  TextModification modForUpdate = pathModMap.get(firstPreviousNodePath);
542  //append node for add to the existing replace modification
543  if(modForUpdate != null){
544  String content = modForUpdate.getNewContent();
545  content += nodeToString(evaluatedNode);
546  modForUpdate.setNewContent(content);
547  }
548  //create new replace modification
549  else{
550  String content = nodeToString(firstPreviousNode);
551  content += nodeToString(evaluatedNode);
552  TextModification newMod = new TextModification(firstPreviousNodePath, null, null, content);
553  removeAllChildMods(pathModMap, firstPreviousNodePath);
554  pathModMap.put(firstPreviousNodePath, newMod);
555  }
556  }
557  else if(firstParentNode != null){
558  String firstParentNodePath = XPathHelper.XPathStringOfNode(firstParentNode);
559  TextModification modForUpdate = pathModMap.get(firstParentNodePath);
560  if(modForUpdate == null){
561  String content = nodeToString(evaluatedNode.getParentNode());
562  TextModification newMod = new TextModification(firstParentNodePath, null, null, content);
563  removeAllChildMods(pathModMap, firstParentNodePath);
564  pathModMap.put(firstParentNodePath, newMod);
565  }
566  //else: It is already replaced
567  }
568  }
569  }
570  return true;
571  } // addNode()
572 
573  /**
574  * Creates a TextModification that removes a node
575  * @param pathModMap Map of the created text modifications
576  * @param removeNode Node for removal
577  * @param highestProtocol highest available protocol version
578  * @param newVersion New version of the document
579  */
580  public static void removeNode(LinkedHashMap<String,TextModification> pathModMap,
581  NodeDetail removeNode, int highestProtocol, Document newVersion){
582  if(!existsParentMod(pathModMap, removeNode.getXpathLocation())){
583  if(highestProtocol < Constants.PROTOCOL_LOD_V2 &&
584  (removeNode.getNode().getNodeName().equalsIgnoreCase("br") ||
585  removeNode.getNode().getNodeName().equalsIgnoreCase("hr")))
586  {
587  //try to replace parent
588  if(!replaceFirstParentNode(pathModMap, removeNode, newVersion, true)){
589  TextModification tm = new TextModification(removeNode.getXpathLocation(), null, null, null);
590  removeAllChildMods(pathModMap, removeNode.getXpathLocation());
591  pathModMap.put(removeNode.getXpathLocation(), tm);
592  }
593  }
594  else{
595  TextModification tm = new TextModification(removeNode.getXpathLocation(), null, null, null);
596  removeAllChildMods(pathModMap, removeNode.getXpathLocation());
597  pathModMap.put(removeNode.getXpathLocation(), tm);
598  }
599  }
600  }
601 
602  /**
603  * Method creates a TextModification that replaces a parent node of some node
604  * Replaced parent node must exist in the version of the document
605  *
606  * @param pathModMap Map of the created text modifications
607  * @param replaceNodeDetail Node for it's parent replace
608  * @param version Version of the document where the parent must exist
609  * @param replaceByDocumentNode If true then parent is replaced by the parent node from the
610  * version of the document (variable version). If true then the parent node is replaced by
611  * the specified Node (variable replaceNodeDetail) parent.
612  * @return True if success, otherwise false
613  */
614  public static boolean replaceFirstParentNode(LinkedHashMap<String,TextModification> pathModMap,
615  NodeDetail replaceNodeDetail, Document version, boolean replaceByDocumentNode){
616  if(!existsParentMod(pathModMap, replaceNodeDetail.getXpathLocation())){
617  Node firstParentNode = null;
618  Node evaluatedNode = replaceNodeDetail.getNode();
619  // gets the first parent node that exists in the version of the document
620  while(firstParentNode == null){
621  firstParentNode = getParentIfExists(version, evaluatedNode);
622  if(firstParentNode == null){
623  evaluatedNode = evaluatedNode.getParentNode();
624  if(evaluatedNode == null){
625  return false;
626  }
627  }
628  }
629  // Creates a replace modification
630  String firstParentNodePath = XPathHelper.XPathStringOfNode(firstParentNode);
631  TextModification modForUpdate = pathModMap.get(firstParentNodePath);
632  if(modForUpdate == null){
633  String content = null;
634  if(replaceByDocumentNode){
635  content = nodeToString(firstParentNode);
636  }
637  else{
638  content = nodeToString(evaluatedNode.getParentNode());
639  }
640  TextModification newMod = new TextModification(firstParentNodePath, null, null, content);
641  removeAllChildMods(pathModMap, firstParentNodePath);
642  pathModMap.put(firstParentNodePath, newMod);
643  }
644  //else: It is already replaced
645  }
646  return true;
647  }
648 
649  /**
650  * Method creates a TextModification that replaces a node
651  *
652  * @param pathModMap Map of the created text modifications
653  * @param replaceNodeDetail Node for replacement
654  */
655  public static void replaceNode(LinkedHashMap<String,TextModification> pathModMap,
656  NodeDetail replaceNodeDetail)
657  {
658  String replacePath = replaceNodeDetail.getXpathLocation();
659  if(!existsParentMod(pathModMap, replacePath)){
660  // Don't replace br or hr
661  if(replaceNodeDetail.getNode().getNodeName().equalsIgnoreCase("br") ||
662  replaceNodeDetail.getNode().getNodeName().equalsIgnoreCase("hr"))
663  {
664  return;
665  }
666  // Replacement of the text node
667  if(replaceNodeDetail.getNode().getNodeType() == Node.TEXT_NODE){
668  TextModification tm = new TextModification(replacePath, null, null, replaceNodeDetail.getNode().getNodeValue());
669  pathModMap.put(replacePath, tm);
670  }
671  // All other nodes replacement
672  else{
673  String content = nodeToString(replaceNodeDetail.getNode());
674  TextModification newMod = new TextModification(replacePath, null, null, content);
675  removeAllChildMods(pathModMap, replacePath);
676  pathModMap.put(replacePath, newMod);
677  }
678  }
679  }
680 } // public class TextModificationsXMLUnitProvider
static void replaceNode(LinkedHashMap< String, TextModification > pathModMap, NodeDetail replaceNodeDetail)
static void removeAllChildMods(LinkedHashMap< String, TextModification > pathModMap, String XPath)
static ArrayList< TextModification > getTextModifications(Document oldVersion, Document newVersion, int highestProtocol)
static boolean addNode(int highestProtocol, LinkedHashMap< String, TextModification > pathModMap, NodeDetail addNodeDetail, Document oldVersion)
Class representing modification of annotated document text.
static ArrayList< TextModification > getTextModifications(String oldVersion, String newVersion, int highestProtocol)
static boolean existsParentMod(LinkedHashMap< String, TextModification > pathModMap, String XPath)
static boolean replaceFirstParentNode(LinkedHashMap< String, TextModification > pathModMap, NodeDetail replaceNodeDetail, Document version, boolean replaceByDocumentNode)
static void removeNode(LinkedHashMap< String, TextModification > pathModMap, NodeDetail removeNode, int highestProtocol, Document newVersion)