From 37ff30223bc41173746bb114c338e6a6ca66129f Mon Sep 17 00:00:00 2001 From: frepe2013 Date: Thu, 3 Jul 2014 21:54:12 +0900 Subject: [PATCH] added method to find rows that match the condition --- .../org/jggug/kobo/gexcelapi/GExcel.groovy | 61 ++++++++++++ .../jggug/kobo/gexcelapi/GExcelTest.groovy | 89 ++++++++++++++++++ src/test/resources/sample.xls | Bin 7680 -> 27136 bytes 3 files changed, 150 insertions(+) diff --git a/src/main/groovy/org/jggug/kobo/gexcelapi/GExcel.groovy b/src/main/groovy/org/jggug/kobo/gexcelapi/GExcel.groovy index ca14922..c989970 100644 --- a/src/main/groovy/org/jggug/kobo/gexcelapi/GExcel.groovy +++ b/src/main/groovy/org/jggug/kobo/gexcelapi/GExcel.groovy @@ -78,6 +78,23 @@ class GExcel { } return null } + findEmptyRow { label -> + Row targetRow = delegate.find { Row row -> + row.rowNum >= CLU.rowIndex(label) && row.getCell(CLU.columnIndex(label))?.value == null } + if (!targetRow) { + return delegate.createRow(delegate.lastRowNum+1) + } + + return targetRow + } + findByCellValue { label, cellValue -> + def condition = createCondition(label, cellValue) + delegate.find(condition) + } + findAllByCellValue { label, cellValue -> + def condition = createCondition(label, cellValue) + delegate.findAll(condition) + } } } @@ -156,6 +173,50 @@ class GExcel { } } + private static createCondition(String label, String cellValue) { + int cellIndex = CLU.columnIndex(label) + def condition = { Row row -> + if (row.rowNum < CLU.rowIndex(label)) { + return false + } + + Cell cell = row.getCell(cellIndex) + if (cell == null) { + return false + } + if (cell.value == null) { + return false + } + if (!cell.isStringType()) { + return false + } + cell.value.contains(cellValue) + } + return condition + } + + private static createCondition(String label, Integer cellValue) { + int cellIndex = CLU.columnIndex(label) + def condition = { Row row -> + if (row.rowNum < CLU.rowIndex(label)) { + return false + } + + Cell cell = row.getCell(cellIndex) + if (cell == null) { + return false + } + if (cell.value == null) { + return false + } + if (!cell.isNumericType()) { + return false + } + cell.value == cellValue + } + return condition + } + private static getRowFromSheetByLabel(sheet, label) { int rowIndex = CLU.rowIndex(label) def row = sheet.getRow(rowIndex) diff --git a/src/test/groovy/org/jggug/kobo/gexcelapi/GExcelTest.groovy b/src/test/groovy/org/jggug/kobo/gexcelapi/GExcelTest.groovy index a04b2aa..f7ae22f 100644 --- a/src/test/groovy/org/jggug/kobo/gexcelapi/GExcelTest.groovy +++ b/src/test/groovy/org/jggug/kobo/gexcelapi/GExcelTest.groovy @@ -19,12 +19,17 @@ package org.jggug.kobo.gexcelapi class GExcelTest extends GroovyTestCase { def sampleFile = "build/resources/test/sample.xls" + def outputPath = "output.xls" + def outputFile def book def sheet + def sheetForFindRowTest void setUp() { book = GExcel.open(sampleFile) sheet = book[0] + sheetForFindRowTest = book[3] + outputFile = new File(outputPath) // dateCellValue is affected by TimeZone. // So it should be explicitly set as GMT in order to avoid causing a failure dependent on an environment. @@ -34,6 +39,8 @@ class GExcelTest extends GroovyTestCase { void tearDown() { book = null sheet = null + sheetForFindRowTest = null + outputFile.delete() } void testOpen() { @@ -294,5 +301,87 @@ class GExcelTest extends GroovyTestCase { assert sheet.getEnclosingMergedRegion(sheet.A4).isFirstCell(sheet.A5) == false assert sheet.getEnclosingMergedRegion(sheet.A4).isFirstCell(sheet.B5) == false } + + void testFindEmptyRowFormulaValueColumn() { + def emptyRow = sheetForFindRowTest.findEmptyRow('A2'); + assert emptyRow != null + assert emptyRow.rowNum == 10 // rowNum start 0 + assert emptyRow.getCell(0)?.value == null + } + + void testFindEmptyRowValueColumn() { + def emptyRow = sheetForFindRowTest.findEmptyRow('B3'); + assert emptyRow != null + assert emptyRow.rowNum == 8 // rowNum start 0 + assert emptyRow.getCell(1)?.value == null + } + + void testAddRow() { + def emptyRow = sheetForFindRowTest.findEmptyRow('A2'); + emptyRow.createCell(0).value = "100" + emptyRow.createCell(1).value = "test" + + outputFile.withOutputStream { book.write(it) } + def targetBook = GExcel.open(outputFile) + def targetSheet = targetBook[3] + assert targetSheet.A11.value == "100" + assert targetSheet.B11.value == "test" + } + + void testFindByCellValue() { + def resultRow = sheetForFindRowTest.findByCellValue('F4', 'Ken'); + assert resultRow != null + assert resultRow.label == "7" // label equals line number on excel + assert resultRow.F_.value == "Ken" + assert resultRow.getCell(5) == sheetForFindRowTest.F7 + } + + void testFindByCellValuePrefixMatch() { + def resultRow = sheetForFindRowTest.findByCellValue('C4', 'Add'); + assert resultRow != null + assert resultRow.label == "4" // label equals line number on excel + assert resultRow.C_.value == "Add Feature" + assert resultRow.getCell(2) == sheetForFindRowTest.C4 + } + + void testFindByCellValueSuffixMatch() { + def resultRow = sheetForFindRowTest.findByCellValue('H6', 'ed'); + assert resultRow != null + assert resultRow.label == "6" // label equals line number on excel + assert resultRow.H_.value == "Closed" + assert resultRow.getCell(7) == sheetForFindRowTest.H6 + } + + void testFindByCellValueNumberMatch() { + def resultRow = sheetForFindRowTest.findByCellValue('I4', 20); + assert resultRow != null + assert resultRow.label == "5" // label equals line number on excel + assert resultRow.I_.value == 20 + assert resultRow.getCell(8) == sheetForFindRowTest.I5 + } + + void testFindAllByCellValue() { + def resultRows = sheetForFindRowTest.findAllByCellValue('D4', 'B'); + assert resultRows[0].label == "5" + assert resultRows[0].D_.value == "B" + assert resultRows[1].label == "6" + assert resultRows[1].D_.value == "B" + } + + void testFindAllByCellValuePrefixMatch() { + def resultRows = sheetForFindRowTest.findAllByCellValue('C4', 'Spec'); + assert resultRows[0].label == "5" + assert resultRows[0].C_.value == "Spec Change" + assert resultRows[1].label == "7" + assert resultRows[1].C_.value == "Spec Change" + } + + void testFindAllByCellValueNumberMatch() { + def resultRows = sheetForFindRowTest.findAllByCellValue('I4', 10); + assert resultRows[0].label == "7" + assert resultRows[0].I_.value == 10 + assert resultRows[1].label == "8" + assert resultRows[1].I_.value == 10 + } } diff --git a/src/test/resources/sample.xls b/src/test/resources/sample.xls index 80cd7804b48ec91f2b34b2fbfa2a46ab31d040bd..b136a06d65cb2ae4d3796e3eeed409bbd25be20a 100644 GIT binary patch literal 27136 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 literal 7680 zcmeHMS!|S56h8kfWm=#sYmxd>1zK8grlqW6XQnLzVPC{V5^c*A3Wbtdj3zDxE=1I55MGRlYcM35fN&pr3t zbI)D=+wZ?CIJ^J-;&YM`=8IRZC9);UMYoV2ku2wnc#tRUS|X81l99a~ZXyd@b#&z% z`Y?%pfO9?!2mskY4lo=b&qyb|iH3Mv)j=$-0ZxEJ8q?{iY-BgjXca=nxGTJzS=Wd|^eS$_F+_#4~jBDUlsZF5>FA$ZuAs7XBYqO&Y=t&_nE!Nb9CgSjmeH`%fh z`(wK-hv%+`$A#e8E`GH1TM=-yN~5&h$XclDmR#Z+AqRLu-`}vjVRd9_iLXH3z|2|8 zFjav~^@z{MN_@rA&E;6ScJ;c3()r0`VPxr=hEj?+*hfUM97@Z5R+shx`6FRtkjZe| zCY_d2`tL)d)AR%Jr=wHqK>W8Z{Qzugb9hcj+PT8mA)ZPvPeZRvL!Xw0o;@Jl4_8xt zcpKDU^zp;xl-~12kfe7HNSCA>o-!^uJ@kI^52lfGs2_c=2SU3f!8E_it7$=3mZpVW zK23|dhG{yg%b$1zi{6qw*aF_t@0%=HvpE*)43W~!8!r}2z;P>#I4mWXc&y@> z4M11`$7tYBoU~Wu7ieORUT_HX-eN~X?owR{I&O<~Y>I81XGf|k?I=)JPa2@E80zwps)F7>Kw~fqlm!h*0`0B`&BlsMl_4x&2POAJqC)Eg;b|a>N z(^;Tq&WAy-ym{&5vZj@_4=cS(zhfW(VKN;>F3K*{obXN478M%rR^CxE3DT@7Qin@O zsq}8!%p+YvsXX$UZR9cl*vVAhOWF2JZ$WzXj`I&s)%tXs~;J_hjqh9Lv@$P!V2JvDha9=Dy0ZgejS} zDY5wHy@-iKckx9a!olL)s;C0x<%u^QL^e-+W)G0;xG^LddIKg&+p!IMzRy!AM|JlJ z_cB2d$fMQRflu}QfBQb|z`NfNe7~P>t98{clD_Rt@kLHzDvfiWN~0I0(l|@0G)}C? z)km)%le^cC$!AI)OgQySdi8jHc{!#&ZsDfOyDsaf71pD}$^9-*-)2mm2YmDsmZ)p7 zzSmHS7o5c^}c8O63>cc~IGs_VM87)7Y!+xlVhRgY8}pP%q=< zfHpnL0lkTYH8znh2UgP!{K|NHIsCKbKu1b02e+%5t)8;`Zr5}t_ts24x=|{Pv*f1H z`4~p)wrzR%38z~)a%NK|jglq^b8J7Mk81_pu7z7b#;%1n8M_u$&LmiPQSf457SfQrV zj9Z!p*YI-BNly)2`DgdM{_EuxoAcj(DIgV7kNn7E?n!|A73--uZ_8nTlf4wcElwT; zV0+wL%zM!er0n6sRrB`4Jl|}>K-bIbcOlU{Q--j{Cer*D5A5rH?JIWKFQn6-eRxiN zb86p=3Hf_ph5nV757&d{PoZr9Z5%-11dndErLO7dsJ>;wjeP%A`=zXbfegjvALhd$ z!g{=?bjVg|(s$^shL}OjS0K(8HxjPFp*D4{#hCLt^t=r(Tq~f%mZ3K9;bZ?C5Bp~5 zsTd@)0CkI5wS$sAlsWx#qaEt_!{B|#5$TtD^w6w2#PmN9ndvj