1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 package org.openoffice.xmerge.merger.merge; 25 26 import org.w3c.dom.Node; 27 import org.w3c.dom.Element; 28 import org.w3c.dom.NodeList; 29 import org.w3c.dom.NamedNodeMap; 30 31 import org.openoffice.xmerge.ConverterCapabilities; 32 import org.openoffice.xmerge.merger.Iterator; 33 import org.openoffice.xmerge.merger.NodeMergeAlgorithm; 34 import org.openoffice.xmerge.merger.diff.CellNodeIterator; 35 import org.openoffice.xmerge.converter.xml.OfficeConstants; 36 import org.openoffice.xmerge.util.XmlUtil; 37 38 39 /** 40 * This is an implementation of the <code>NodeMergeAlgorithm</code> 41 * interface. It is used to merge two rows using a positional 42 * comparison base method. 43 */ 44 public final class PositionBaseRowMerge implements NodeMergeAlgorithm { 45 46 /** The capabilities of this converter. */ 47 private ConverterCapabilities cc_; 48 49 50 /** 51 * Constructor. 52 * 53 * @param cc The <code>ConverterCapabilities</code>. 54 */ PositionBaseRowMerge(ConverterCapabilities cc)55 public PositionBaseRowMerge(ConverterCapabilities cc) { 56 cc_ = cc; 57 } 58 59 merge(Node orgRow, Node modRow)60 public void merge(Node orgRow, Node modRow) { 61 62 Iterator orgCells = new CellNodeIterator(cc_, orgRow); 63 Iterator modCells = new CellNodeIterator(cc_, modRow); 64 65 mergeCellSequences(orgCells, modCells); 66 } 67 68 69 // used to compare the cell 1 by 1 mergeCellSequences(Iterator orgSeq, Iterator modSeq)70 private void mergeCellSequences(Iterator orgSeq, Iterator modSeq) { 71 72 boolean needMerge = true; 73 Element orgCell, modCell; 74 75 Object orgSeqObject = orgSeq.start(); 76 Object modSeqObject = modSeq.start(); 77 78 while (orgSeqObject != null) { 79 80 81 needMerge = true; 82 83 if (modSeqObject == null) { 84 // no corresponding cell in the target, empty out the cell 85 SheetUtil.emptyCell(cc_, (Node)orgSeqObject); 86 orgSeqObject = orgSeq.next(); 87 88 } else { 89 90 // compare the cell directly 91 if (!orgSeq.equivalent(orgSeqObject, modSeqObject)) { 92 93 orgCell = (Element)orgSeqObject; 94 modCell = (Element)modSeqObject; 95 96 // check whether the original cell with multiple column 97 // if so, need to split one out for merge 98 String orgColRepeated = orgCell.getAttribute( 99 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); 100 String modColRepeated = modCell.getAttribute( 101 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); 102 103 int orgColNum = 1; 104 int modColNum = 1; 105 106 if (orgColRepeated.length() > 0) { 107 orgColNum = 108 Integer.valueOf(orgColRepeated).intValue(); 109 } 110 if (modColRepeated.length() > 0) { 111 modColNum = 112 Integer.valueOf(modColRepeated).intValue(); 113 } 114 115 // try to find out the common number of repeated cols 116 if (orgColNum == modColNum) { 117 orgSeqObject = orgSeq.next(); 118 modSeqObject = modSeq.next(); 119 120 // cut the original cell into 2 half, first half 121 // have the repeated attribute = modify cell attr 122 } else if (orgColNum > modColNum) { 123 Element orgSplitCell = splitColRepeatedCell( 124 orgCell, modColNum, 125 orgColNum - modColNum); 126 // it may equal after the split! 127 if (orgSeq.equivalent(orgSplitCell, modCell)) { 128 needMerge = false; 129 } 130 orgCell = orgSplitCell; 131 modSeqObject = modSeq.next(); 132 133 // cut the modified cell into 2 half, first half 134 // have the repeated attribute = original cell attr 135 } else { 136 Element modSplitCell = splitColRepeatedCell( 137 modCell, orgColNum, 138 modColNum - orgColNum); 139 // it may equal after the split! 140 if (modSeq.equivalent(orgCell, modSplitCell)) { 141 needMerge = false; 142 } 143 modCell = modSplitCell; 144 orgSeqObject = orgSeq.next(); 145 } 146 147 if (needMerge) { 148 mergeCells(orgCell, modCell); 149 } 150 151 } else { 152 // cells are equivalent, move on to next one. 153 orgSeqObject = orgSeq.next(); 154 modSeqObject = modSeq.next(); 155 } // end if-else 156 } // end if-else 157 } // end while loop 158 159 // get the one of the original cell, so that the cloned node 160 // can base it to find the document node 161 orgCell = (Element)orgSeq.start(); 162 163 // add any extra cells to the original cell sequence. 164 for (; modSeqObject != null; modSeqObject = modSeq.next()) { 165 Node clonedNode = XmlUtil.deepClone(orgCell, (Node)modSeqObject); 166 Node parent = orgCell.getParentNode(); 167 parent.appendChild(clonedNode); 168 } 169 } 170 171 splitColRepeatedCell(Element orgCell, int splitNum, int orgNum)172 private Element splitColRepeatedCell(Element orgCell, 173 int splitNum, int orgNum) { 174 // NOTE: should we really want to do deep clone? 175 // in most the case, it is an empty cell, but the 176 // specification didn't forbid any node to use multiple 177 // column attributes. i.e. the node can contain text 178 // nodes or other things under it. 179 Element splitCell = (Element)(orgCell.cloneNode(true)); 180 181 if (splitNum > 1) { 182 splitCell.setAttribute( 183 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, 184 String.valueOf(splitNum)); 185 } else if (splitNum == 1) { 186 splitCell.removeAttribute( 187 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); 188 } 189 if (orgNum > 1) { 190 orgCell.setAttribute( 191 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, 192 String.valueOf(orgNum)); 193 } else if (orgNum == 1) { 194 orgCell.removeAttribute( 195 OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED); 196 } 197 198 Node parentNode = orgCell.getParentNode(); 199 parentNode.insertBefore(splitCell, orgCell); 200 201 return splitCell; 202 } 203 204 mergeCells(Element orgCell, Element modCell)205 private void mergeCells(Element orgCell, Element modCell) { 206 207 // remove all the supported attributes and possible text child for 208 // string cells 209 SheetUtil.emptyCell(cc_, orgCell); 210 211 // copy all the supported attributes and possible text child from 212 // the modified cell 213 NamedNodeMap attrNodes = modCell.getAttributes(); 214 215 if (attrNodes != null) { 216 217 // copy the first text:p node. As it's not necessary only string 218 // type cell can have a text:p section. 219 NodeList paraNodes = 220 modCell.getElementsByTagName(OfficeConstants.TAG_PARAGRAPH); 221 222 Node firstParaNode = paraNodes.item(0); 223 224 // try to clone the node 225 if (firstParaNode != null) { 226 227 Node clonedNode = XmlUtil.deepClone(orgCell, firstParaNode); 228 229 // insert as the first child of the original cell 230 Node firstChild = orgCell.getFirstChild(); 231 if (firstChild != null) { 232 orgCell.insertBefore(clonedNode, firstChild); 233 } else { 234 orgCell.appendChild(clonedNode); 235 } 236 } 237 238 // check all the attributes and copy those we supported in 239 // converter 240 // NOTE: for attribute list, refer to section 4.7.2 in specification 241 int len = attrNodes.getLength(); 242 243 for (int i = 0; i < len; i++) { 244 Node attr = attrNodes.item(i); 245 246 // copy the supported attrs 247 if (cc_.canConvertAttribute(OfficeConstants.TAG_TABLE_CELL, 248 attr.getNodeName())) { 249 orgCell.setAttribute(attr.getNodeName(), 250 attr.getNodeValue()); 251 } 252 } 253 } 254 } 255 } 256 257