mirror of
https://github.com/optilude/xlsx-template.git
synced 2026-07-02 08:27:39 +08:00
Merge branch 'refactor-for-row-col-insertion'
This commit is contained in:
+192
-106
@@ -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;
|
||||
})();
|
||||
@@ -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 ]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user