Merge branch 'refactor-for-row-col-insertion'

This commit is contained in:
Martin Aspeli
2013-09-14 14:28:31 +01:00
3 changed files with 238 additions and 106 deletions
+192 -106
View File
@@ -49,122 +49,92 @@ module.exports = (function() {
Workbook.prototype.substitute = function(sheet, substitutions) {
var self = this;
function getCurrentRow(row, rowsInserted) {
return parseInt(row.attrib.r, 10) + rowsInserted;
}
function getCurrentCell(cell, currentRow, cellsInserted) {
var colRef = self.splitRef(cell.attrib.r).col,
colNum = self.charToNum(colRef);
return self.joinRef({
row: currentRow,
col: self.numToChar(colNum + cellsInserted)
});
}
var file = WORKSHEETS + "sheet" + sheet + ".xml",
tree = etree.parse(self.archive.file(file).asText()),
root = tree.getroot();
// TODO: Find tables
// - Load refs from ./tableParts/tablePart@r:id
// - Find corresponding table file in worksheets/_rels/sheet${num}.xml.rels under Relationships/Relationship@Target
// - Load table XML file. Record table@ref (range) and table/autofilter@ref (range)
var sheetData = root.find("sheetData"),
currentRow = null,
rowsInserted = 0,
totalRowsInserted = 0,
rows = [];
sheetData.findall("row").forEach(function(row) {
var appendRow = true;
row.attrib.r = currentRow = getCurrentRow(row, rowsInserted);
row.attrib.r = currentRow = self.getCurrentRow(row, totalRowsInserted);
rows.push(row);
var cells = [],
currentCell = null,
cellsInserted = 0;
totalCellsInserted = 0,
newTableRows = [];
row.findall("c").forEach(function(cell) {
var appendCell = true;
cell.attrib.r = currentCell = getCurrentCell(cell, currentRow, cellsInserted);
cell.attrib.r = self.getCurrentCell(cell, currentRow, totalCellsInserted);
// If c[@t="s"] (string column), look up /c/v@text as integer in
// `this.sharedStrings`
if(cell.attrib.t === "s") {
// Look for a shared string that may contain placeholders
var cellValue = cell.find("v"),
stringIndex = parseInt(cellValue.text, 10),
string = self.sharedStrings[stringIndex];
// Look for a shared string that may contain placeholders
if(string !== undefined) {
// Loop over placeholders
var placeholders = self.extractPlaceholders(string);
placeholders.forEach(function(placeholder) {
// Only substitute things for which we have a substition
var substitution = substitutions[placeholder.name];
if(substitution === undefined) {
return;
}
var substituted = self.substituteScalar(cell, string, placeholder, substitution);
if(substituted !== null) {
string = substituted; // in case we have multiple placeholders
} else if(placeholder.full && substitution instanceof Array) {
// A table substitution (rows)
if(placeholder.type === "table") { // multiple rows
// TODO
// A list substitution (columns)
} else if(placeholder.type === "normal") {
appendCell = false; // don't double-insert cells
--cellsInserted; // we technically delete one before we start adding back
// add a cell for each element in the list
substitution.forEach(function(substitutionElement) {
++cellsInserted;
if(cellsInserted > 0) {
currentCell = self.nextCol(currentCell);
}
var newCol = self.cloneElement(cell);
self.insertCellValue(newCol, substitutionElement);
newCol.attrib.r = currentCell;
cells.push(newCol);
});
}
}
});
if(string === undefined) {
return;
}
// Loop over placeholders
self.extractPlaceholders(string).forEach(function(placeholder) {
// Only substitute things for which we have a substitution
var substitution = substitutions[placeholder.name];
if(substitution === undefined) {
return;
}
if(placeholder.full && placeholder.type === "table" && substitution instanceof Array) {
totalRowsInserted += self.substituteTable(
rows, row, newTableRows, totalRowsInserted,
cells, cell, substitution, placeholder.key
);
} else if(placeholder.full && placeholder.type === "normal" && substitution instanceof Array) {
appendCell = false; // don't double-insert cells
totalCellsInserted += self.substituteArray(
cells, cell, substitution
);
} else {
string = self.substituteScalar(cell, string, placeholder, substitution);
}
});
}
// if we are inserting rows or columns, we may not want to keep the original cell anymore
// if we are inserting columns, we may not want to keep the original cell anymore
if(appendCell) {
cells.push(cell);
}
});
}); // cells loop
if(appendRow) {
rows.push(row);
// We may have inserted columns, so re-build the children of the row
self.replaceChildren(row, cells);
// We may have inserted columns, so re-build the children of the row
self.replaceChildren(row, cells);
// Update row spans attribute
if(cellsInserted !== 0 && row.attrib.spans) {
var rowSpan = row.attrib.spans.split(':').map(function(f) { return parseInt(f, 10); });
rowSpan[1] += cellsInserted;
row.attrib.spans = rowSpan.join(":");
}
// Update row spans attribute
if(totalCellsInserted !== 0 && row.attrib.spans) {
var rowSpan = row.attrib.spans.split(':').map(function(f) { return parseInt(f, 10); });
rowSpan[1] += totalCellsInserted;
row.attrib.spans = rowSpan.join(":");
}
});
}); // rows loop
// We may have inserted rows, so re-build the children of the sheetData
self.replaceChildren(sheetData, rows);
// TODO: Update <mergeCells />
// Write back the modified XML tree
self.archive.file(file, etree.tostring(root));
};
@@ -345,6 +315,25 @@ module.exports = (function() {
return str;
};
// Is ref inside the table defined by startRef and endRef?
Workbook.prototype.isWithin = function(ref, startRef, endRef) {
var self = this;
var start = self.splitRef(startRef),
end = self.splitRef(endRef),
target = self.splitRef(ref);
start.col = self.charToNum(start.col);
end.col = self.charToNum(end.col);
target.col = self.charToNum(target.col);
return (
start.row <= target.row && target.row <= end.row &&
start.col <= target.col && target.col <= end.col
);
};
Workbook.prototype.stringify = function (value) {
var self = this;
@@ -376,48 +365,129 @@ module.exports = (function() {
cell.attrib.t = "s";
cellValue.text = self.stringIndex(stringified);
}
return stringified;
};
Workbook.prototype.substituteScalar = function(cell, string, placeholder, substitution) {
var self = this;
var cellValue = cell.find("v"),
stringified = self.stringify(substitution),
newString = stringified;
if(placeholder.full && typeof(substitution) === "number") {
cell.attrib.t = "n";
cellValue.text = stringified;
} else if(placeholder.full && typeof(substitution) === "boolean" ) {
cell.attrib.t = "b";
cellValue.text = stringified;
} else if(placeholder.full && substitution instanceof Date) {
cell.attrib.t = "d";
cellValue.text = stringified;
} else if(placeholder.full && typeof(substitution) === "string") {
cell.attrib.t = "s";
self.replaceString(string, stringified);
} else if(!placeholder.full) {
cell.attrib.t = "s";
newString = string.replace(placeholder.placeholder, stringified);
self.replaceString(string, newString);
} else {
return null;
if(placeholder.full && typeof(substitution) === "string") {
self.replaceString(string, substitution);
}
if(placeholder.full) {
return self.insertCellValue(cell, substitution);
} else {
var newString = string.replace(placeholder.placeholder, self.stringify(substitution));
cell.attrib.t = "s";
self.replaceString(string, newString);
return newString;
}
return newString;
};
// Perform a columns substitution.
Workbook.prototype.substituteArray = function(cells, cell, substitution) {
var self = this;
Workbook.prototype.cloneElement = function(element) {
var newCellsInserted = -1, // we technically delete one before we start adding back
currentCell = cell.attrib.r;
// add a cell for each element in the list
substitution.forEach(function(element) {
++newCellsInserted;
if(newCellsInserted > 0) {
currentCell = self.nextCol(currentCell);
}
var newCell = self.cloneElement(cell);
self.insertCellValue(newCell, element);
newCell.attrib.r = currentCell;
cells.push(newCell);
});
return newCellsInserted;
};
// Perform a table substitution. May update `newTableRows` and change `cell`.
// Returns total number of new rows inserted.
Workbook.prototype.substituteTable = function(
rows, row, newTableRows, totalRowsInserted,
cells, cell, substitution, key
) {
var self = this,
newRowsInserted = 0;
// if no elements, blank the cell, but don't delete it
if(substitution.length === 0) {
delete cell.attrib.t;
self.replaceChildren(cell, []);
} else {
substitution.forEach(function(substitutionElement, idx) {
var newRow, newCell,
value = substitutionElement[key];
if(idx === 0) {
newCell = cell;
} else {
// Do we have an existing row to use? If not, create one.
if((idx - 1) < newTableRows.length) {
newRow = newTableRows[idx - 1];
} else {
newRow = self.cloneElement(row, false);
++newRowsInserted;
++totalRowsInserted;
newRow.attrib.r = self.getCurrentRow(row, totalRowsInserted);
rows.push(newRow);
newTableRows.push(newRow);
}
// Create a new cell
newCell = self.cloneElement(cell);
newCell.attrib.r = self.joinRef({
row: newRow.attrib.r,
col: self.splitRef(newCell.attrib.r).col
});
// Add cell to new row
newRow.append(newCell);
}
if(value instanceof Array) {
// TODO: Deal with lists as values.
// TODO: Update spans attribute on inserted row
self.insertCellValue(newCell, value);
} else {
self.insertCellValue(newCell, value);
}
});
// TODO: Update table definitions
}
return newRowsInserted;
};
Workbook.prototype.cloneElement = function(element, deep) {
var self = this;
var newElement = etree.Element(element.tag, element.attrib);
newElement.text = element.text;
newElement.tail = element.tail;
element.getchildren().forEach(function(child) {
newElement.append(self.cloneElement(child));
});
if(deep !== false) {
element.getchildren().forEach(function(child) {
newElement.append(self.cloneElement(child));
});
}
return newElement;
};
@@ -428,5 +498,21 @@ module.exports = (function() {
});
};
Workbook.prototype.getCurrentRow = function(row, rowsInserted) {
return parseInt(row.attrib.r, 10) + rowsInserted;
};
Workbook.prototype.getCurrentCell = function(cell, currentRow, cellsInserted) {
var self = this;
var colRef = self.splitRef(cell.attrib.r).col,
colNum = self.charToNum(colRef);
return self.joinRef({
row: currentRow,
col: self.numToChar(colNum + cellsInserted)
});
};
return Workbook;
})();
+4
View File
@@ -78,6 +78,10 @@ describe("CRUD operations", function() {
name: "James Smith",
role: "Analyst",
days: [4, 4,4 ]
}, {
name: "Jim Smith",
role: "Manager",
days: [4, 4,4 ]
}
]
});
+42
View File
@@ -292,6 +292,48 @@ describe("Helpers", function() {
});
describe('isWithin', function() {
it("can check 1x1 cells", function() {
var t = new XlsxTemplate();
expect(t.isWithin("A1", "A1", "A1")).toEqual(true);
expect(t.isWithin("A2", "A1", "A1")).toEqual(false);
expect(t.isWithin("B1", "A1", "A1")).toEqual(false);
});
it("can check 1xn cells", function() {
var t = new XlsxTemplate();
expect(t.isWithin("A1", "A1", "A3")).toEqual(true);
expect(t.isWithin("A3", "A1", "A3")).toEqual(true);
expect(t.isWithin("A4", "A1", "A3")).toEqual(false);
expect(t.isWithin("A5", "A1", "A3")).toEqual(false);
expect(t.isWithin("B1", "A1", "A3")).toEqual(false);
});
it("can check nxn cells", function() {
var t = new XlsxTemplate();
expect(t.isWithin("A1", "A2", "C3")).toEqual(false);
expect(t.isWithin("A3", "A2", "C3")).toEqual(true);
expect(t.isWithin("B2", "A2", "C3")).toEqual(true);
expect(t.isWithin("A5", "A2", "C3")).toEqual(false);
expect(t.isWithin("D2", "A2", "C3")).toEqual(false);
});
it("can check large nxn cells", function() {
var t = new XlsxTemplate();
expect(t.isWithin("AZ1", "AZ2", "CZ3")).toEqual(false);
expect(t.isWithin("AZ3", "AZ2", "CZ3")).toEqual(true);
expect(t.isWithin("BZ2", "AZ2", "CZ3")).toEqual(true);
expect(t.isWithin("AZ5", "AZ2", "CZ3")).toEqual(false);
expect(t.isWithin("DZ2", "AZ2", "CZ3")).toEqual(false);
});
});
describe('stringify', function() {
it("can stringify dates", function() {