{"id":26600805,"url":"https://github.com/mashingan/excelin","last_synced_at":"2025-04-09T16:24:03.519Z","repository":{"id":80051337,"uuid":"484110976","full_name":"mashingan/excelin","owner":"mashingan","description":"Create and read Excel file purely in Nim","archived":false,"fork":false,"pushed_at":"2023-06-22T14:19:49.000Z","size":611,"stargazers_count":50,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T18:48:02.748Z","etag":null,"topics":["excel","library","nim","nim-lang","spreadsheet"],"latest_commit_sha":null,"homepage":"","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mashingan.png","metadata":{"files":{"readme":"readme.md","changelog":"changelogs.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-21T15:42:50.000Z","updated_at":"2025-01-24T23:36:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"5a071c56-eee0-481e-bd42-256b06e5725b","html_url":"https://github.com/mashingan/excelin","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fexcelin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fexcelin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fexcelin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashingan%2Fexcelin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mashingan","download_url":"https://codeload.github.com/mashingan/excelin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248066020,"owners_count":21042019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["excel","library","nim","nim-lang","spreadsheet"],"created_at":"2025-03-23T18:35:30.189Z","updated_at":"2025-04-09T16:24:03.510Z","avatar_url":"https://github.com/mashingan.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Excelin - create and read Excel pure Nim\n\n[![Build Status](https://nimble.directory/ci/badges/excelin/version.svg)](https://nimble.directory/ci/badges/excelin/nimdevel/output.html) [![Build Status](https://nimble.directory/ci/badges/excelin/nimdevel/status.svg)](https://nimble.directory/ci/badges/excelin/nimdevel/output.html) [![Build Status](https://nimble.directory/ci/badges/excelin/nimdevel/docstatus.svg)](https://nimble.directory/ci/badges/excelin/nimdevel/doc_build_output.html)\n\nA library to work with Excel file and/or data.\n\n## Docs\n\nAll available APIs can be find in [docs page](https://mashingan.github.io/excelin/src/htmldocs/excelin.html).\n\n# Examples\n\n* [Common operations](#common-operations)\n* [Working with sheets](#working-with-sheets)\n* [Cell formula](#cell-formula)\n* [Cell styling](#cell-styling)\n* [Row display](#row-display)\n* [Sheet auto filter](#sheet-auto-filter)\n* [Cell merge](#cell-merge)\n* [Sheet page breaks](#sheet-page-breaks)\n\n## Common operations\nAll operations available working with Excel worksheet are illustrated in below:\n\n```nim\nfrom std/times import now, DateTime, Time, toTime, parse, Month,\n    month, year, monthday, toUnix, `$`\nfrom std/strformat import fmt\nfrom std/sugar import `-\u003e`, `=\u003e`, dump\nfrom std/strscans import scanf\nfrom std/sequtils import toSeq\nimport excelin\n\n# `newExcel` returns Excel and Sheet object to immediately work\n# when creating an Excel data.\nlet (excel, sheet) = newExcel()\n\n# we of course can also read from Excel file directly using `readExcel`\n# we comment this out because the path is imaginary\n#let excelTemplate = readExcel(\"path/to/template.xlsx\")\n# note readExcel only returns the Excel itself because there's no\n# known default sheet available. Use `excelin.getSheet(Excel,string): Sheet`\n# to get the sheet based on its name.\n\ndoAssert sheet.name == \"Sheet1\"\n# by default the name sheet is Sheet1\n\n# let's change it to other name\nsheet.name = \"excelin-example\"\ndoAssert sheet.name == \"excelin-example\"\n\n# let's add/fetch some row to our sheet\nlet row1 = sheet.row 1\n\n# excelin.row is immediately creating when the rows if it's not available\n# and if it's available, it's returning the existing.\n# With excelin.rowNum, we can check its row number.\ndoAssert row1.rowNum == 1\n\n# let's add another row, this time it's row 5\nlet row5 = sheet.row 5\ndoAssert row5.rowNum == 5\n\n# in this case, we immediately get the row 5 even though the existing\n# rows in the sheet are only one.\n\ntype\n    ForExample = object\n        a: string\n        b: int\n\nproc `$`(f: ForExample): string = fmt\"[{f.a}:{f.b}]\"\n\n# let's put some values in row cells\nlet nao = now()\nrow1[\"A\"] = \"this is string\"\nrow1[\"C\"] = 256\nrow1[\"E\"] = 42.42\n\nrow1[\"B\"] = nao # Excelin support DateTime or Time and\n                # by default it will be formatted as yyyy-MM-dd'T'HH:mm:dd.fff'.'zz\n                # e.g.: 2200-12-01T22:10:23.456+01\n\nrow1[\"D\"] = \"2200/12/01\" # we put the date as string for later example when fetching\n                         # using supplied converter function from cell value\n\nrow1[\"F\"] = $ForExample(a: \"A\", b: 200)\nrow1[\"H\"] = -111\nlet srclink = Hyperlink(\n  target: \"https://github.com/mashingan/excelin\",\n  text: \"excelin github page\",\n  tooltip: \"lakad matataag, excelin excelin\",\n)\nrow1[\"J\"] = srclink\n\n# notice above example we arbitrarily chose the column and by current implementation\n# Excel data won't add unnecessary empty cells. In other words, sparse row cells.\n# When we're sure working with large cells and often have to update its cell value,\n# we can supply the optional argument `cfFilled` to make our cells in the row filled\n# preemptively.\n\nlet row2 = sheet.row(2, cfFilled) # default is cfSparse\nclear row2\n\n# While example above the new row is already empty by default,\n# we can clear all cells in the row with `clear` proc.\n \n# now let's fetch the data we inputted\ndoAssert row1[\"A\", string] == \"this is string\"\ndoAssert row1.getCell[:uint](\"C\") == 256\ndoAssert row1[\"H\", int] == -111\ndoAssert row1[\"B\", DateTime].toTime.toUnix == nao.toTime.toUnix\ndoAssert row1[\"B\", Time].toUnix == nao.toTime.toUnix\ndoAssert row1[\"E\", float] == 42.42\nlet destlink = row1[\"J\", Hyperlink]\ndoAssert destlink.target == srclink.target\ndoAssert destlink.text == srclink.text\ndoAssert destlink.tooltip == srclink.tooltip\n\n# in above example, we fetched various values from its designated cell position\n# using the two kind of function, `getCell` and `[]`. `[]` often used for\n# elementary/primitive types those supported by Excelin by default. `getCell`\n# has 3rd parameter, a closure with signature `string -\u003e R`, which default to `nil`,\n# that will give users the flexibility to read the string value representation\n# to the what intended to convert. We'll see it below\n#\n# note also that we need to compare to its second for DateTime|Time instead of directly using\n# times.`==` because the comparison precision up to nanosecond, something we\n# can't provide in this example\n\nlet dt = row1.getCell[:DateTime](\"D\",\n  (s: string) -\u003e DateTime =\u003e (\n    dump s; result = parse(s, \"yyyy/MM/dd\"); dump result))\ndoAssert dt.year == 2200\ndoAssert dt.month == mDec\ndoAssert dt.monthday == 1\n\nlet fex = row1.getCell[:ForExample](\"F\", func(s: string): ForExample =\n    discard scanf(s, \"[$w:$i]\", result.a, result.b)\n)\ndoAssert fex.a == \"A\"\ndoAssert fex.b == 200\n\n# above examples we provide two example of using closure for converting\n# string representation of cell value to our intended object. With this,\n# users can roll their own conversion way to interpret the cell data.\n\n# Following the pattern like sequtils.map with sequtils.mapIt and others,\n# we also provide the shorthand with excelin.getCellIt\n\nlet dtIt = row1.getCellIt[:DateTime](\"D\", parse(it, \"yyyy/MM/dd\"))\ndoAssert dtIt.year == 2200\ndoAssert dtIt.month == mDec\ndoAssert dtIt.monthday == 1\n\nlet fexIt = row1.getCellIt[:ForExample](\"F\", (\n    discard scanf(it, \"[$w:$i]\", result.a, result.b)))\ndoAssert fexIt.a == \"A\"\ndoAssert fexIt.b == 200\n\n# We also provide helpers `toNum` and `toCol` to convert string-int column\n# representation. Usually when we're working with array/seq of data,\n# we want to access the column string but we only have the int, so this\n# helpers will come handy.\n\nlet row11 = sheet.row 11\nfor i in 0 ..\u003c 10: # both toCol and toNum is starting from zero.\n    row11[i.toCol] = i.toCol\n    \n# and let's see whether it's same or not\nfor i, c in toSeq['A'..'J']:\n    doAssert row11[$c, string].toNum == i\n\n\n# Starting from version 0.5.0, we add rows iterator for sheet and colums iterator\n# for row together with its last accessor. Let's see it in action.\n\ndoAssert row1.lastCol == \"J\"        # lastCol returning string column\ndoAssert row11.lastCol == \"J\"\ndoAssert sheet.lastRow.rowNum == 11 # lastRow returning Row\n\nfor col in row11.cols:\n  stdout.write col\necho()\n# will print out:\n#ABCDEFGHIJ\n\nfor row in sheet.rows:\n  stdout.write row.rowNum, \", \"\n# will print out:\n#1, 11, \n#\n# note that row 2 and 5 are not shown because both are empty row even though we did access both row.\n# Fill with some value in its cells to make it not empty, and `Row.clear` does the reverse\n# by clearing all cells in a row.\n\ndoAssert row2.empty\ndoAssert row5.empty\n\n# finally, we have 2 options to access the binary Excel data, using `$` and\n# `writeFile`. Both of procs are the usual which `$` is stringify (that's\n# to return the string of Excel) and `writeFile` is accepting string path\n# to where the Excel data will be written.\n\nlet toSendToWire = $excel\nexcel.writeFile(\"excelin-example-readme.xlsx\")\n\n# note that the current excelin.`$` is using the `writeFile` first to temporarily\n# write to file in $TEMP dir because the current zip lib dependency doesn't\n# provide the `$` to get the raw data from built zip directly.\n```\n\n[Back to examples list](#examples)\n\n\n## Working-with-sheets\n\nAnother example here we work directly with `Sheet` instead of the `Rows` and/or cells.\n\n```nim\nimport excelin\n\n# prepare our excel\nlet (excel, _) = newExcel()\ndoAssert excel.sheetNames == @[\"Sheet1\"]\n\n# above we see that our excel has seq string with a member\n# \"Sheet1\". The \"Sheet1\" is the default sheet when creating\n# a new Excel file.\n# Let's add a sheet to our Excel.\n\nlet newsheet = excel.addSheet \"new-sheet\"\ndoAssert newsheet.name == \"new-sheet\"\ndoAssert excel.sheetNames == @[\"Sheet1\", \"new-sheet\"]\n\n# above, we add a new sheet with supplied of the new-sheet name.\n# By checking with `sheetNames` proc, we see two sheets' name.\n\n# Let's see what happen when we add a sheet without supplying the name\nlet sheet3 = excel.addSheet\ndoAssert sheet3.name == \"Sheet3\"\ndoAssert excel.sheetNames == @[\"Sheet1\", \"new-sheet\", \"Sheet3\"]\n\n# While the default name quite unexpected, we can guess the \"num\" part\n# for default sheet naming is related to how many we added/invoked\n# the `addSheet` proc. We'll see below example why it's done like this.\n\n# Let's add again\nlet anewsheet = excel.addSheet \"new-sheet\"\ndoAssert anewsheet.name == \"new-sheet\"\ndoAssert excel.sheetNames == @[\"Sheet1\", \"new-sheet\", \"Sheet3\", \"new-sheet\"]\n\n# Here, we added a new sheet using existing sheet name.\n# This can be done because internally Excel workbook holds the reference of\n# sheets is by using its id instead of the name. Hence adding a same name\n# for new sheet is possible.\n# For the consquence, let's see what happens when we delete a sheet below\n\n# work fine case\nexcel.deleteSheet \"Sheet1\"\ndoAssert excel.sheetNames == @[\"new-sheet\", \"Sheet3\", \"new-sheet\"]\n\n# deleting sheet with name \"new-sheet\"\n\nexcel.deleteSheet \"new-sheet\"\ndoAssert excel.sheetNames == @[\"Sheet3\", \"new-sheet\"]\n\n# will delete the older one since it's the first the sheet found with \"new-sheet\" name\n# when there's no name available, Excel file will do nothing.\n\nexcel.deleteSheet \"aww-sheet\"\ndoAssert excel.sheetNames == @[\"Sheet3\", \"new-sheet\"]\n\n# still same as before.\n# Below example we illustrate how to get by sheet name.\n\nanewsheet.row(1)[\"A\"] = \"temptest\"\ndoAssert anewsheet.row(1)[\"A\", string] == \"temptest\"\ndiscard excel.addSheet \"new-sheet\" # add a new to make it duplicate\nlet foundOlderSheet = excel.getSheet \"new-sheet\"\ndoAssert foundOlderSheet.row(1)[\"A\", string] == \"temptest\"\n\n# Here we get sheet by name, and like deleting the sheet, fetching/getting\n# the sheet also returning the older sheet of the same name.\n\ndoAssert excel.sheetNames == @[\"Sheet3\", \"new-sheet\", \"new-sheet\"]\nexcel.writeFile (\"many-sheets.xlsx\")\n\n# Write it to file and open it with our favorite Excel viewer to see 3 sheets:\n# Sheet3, new-sheet and new-sheet.\n# Using libreoffice to view the Excel file, the duplicate name will be appended with\n# format {sheetName}-{numDuplicated}.\n# We can replicate that behaviour too but currently we support duplicate sheet name.\n```\n\n[Back to examples list](#examples)\n\n## Cell Formula\n\nWe support rudimentary of filling and fetching cell with format of Formula.\n\n```nim\nfrom std/math import cbrt\nfrom excelin import\n    newExcel,   # the usual for creating empty excel\n    row,        # for fetching row from sheet\n    toCol,      # to get string column from integer\n    Formula,    # the cell type object this example for.\n    `[]=`,      # fill cell\n    `[]`,       # fetch cell\n    writeFile,  # finally, to write to file\n\nlet (excel, sheet) = newExcel()\nlet row1 = sheet.row 1\n\n# Let's setup some simple data in a row 1 with col A..J simple seq of int\n\nvar sum = 0 # this will be our calculated result\nfor i in 0 .. 9:\n    row1[i.toCol] = i\n    sum += i\n\n# Here, we simply fill A1 = 0, B1 = 1, ... J1 = 9\n# while the equation is to sum values from cell A to J in K1.\n# Additionally, we'll add another example which depend\n# on another formula cell with equation CUBE(K1) in L1\n\nrow1[10.toCol] = Formula(equation: \"SUM(A1:J1)\", valueStr: $sum)\nlet cubesum = cbrt(float64 sum)\nrow1[11.toCol] = Formula(equation: \"CUBE(K1)\", valueStr: $cubesum)\n\n# Formula has two public fields, equation which is the formula string itself\n# and valueStr, the string of calcluated value.\n# Let's fetch and check it\n\nlet fmr = row1[\"k\", Formula]\ndoAssert fmr.equation == \"SUM(A1:J1)\"\ndoAssert fmr.valueStr == \"45\"\nlet f1l = row1[\"l\", Formula]\ndoAssert f1l.equation == \"CUBE(K1)\"\ndoAssert f1l.valueStr == $cubesum\n\n# As this is rudimentary support for formula format, the equation itself\n# is simply string that we'll fill to cell, and the value is something\n# that we calculate manually on our end.\n\n# What if we fill another formula with its equation only? The value is simply\n# nothing since we didn't fill its value.\n\nlet row1[\"m\"] = Formula(equation: \"L1\")\nlet f1m = row1[\"m\", Formula]\ndoAssert f1m.equation == \"L1\"\ndoAssert f1m.valueStr == \"\"\n\n# lastly, as usual, let's write to file and check it other excel viewer apps.\n# note for cell M1 as we supplied empty value in above.\nexcel.writeFile \"excelin-sum-example.xlsx\"\n```\n\n[Back to examples list](#examples)\n\n## Cell styling\n\nIn this example we'll see various styling provided for cells in row.\n\n```nim\nimport std/colors # to work with coloring\nfrom std/sequtils import repeat\nfrom std/strutils import join\nfrom excelin import\n    newExcel,\n    row,\n    `[]=`,\n    \n    BorderProp,  # The object of Border which style is ready to be applied\n    BorderStyle, # Enum for selecting border style,\n                 # not to confuse with `borderStyle` proc for setting up\n                 # style mentioned below section\n    PatternType, # Enum for selecting fill style pattern\n\n    # This part of APIs is for setting up the style.\n    # The naming pattern is `{objectTypeName}style` viz.\n    # Font -\u003e fontStyle\n    # Border -\u003e borderStyle\n    # etc\n    style,\n    fontStyle,\n    borderStyle,\n    borderPropStyle,\n    fillStyle,\n    patternFillStyle,\n\n    # For linking style between cells\n    shareStyle,\n    copyStyle,\n    \n    # This part of APIs is for getting the style.\n    # The naming pattern is `style{ObjectTypeName}` viz.\n    # Font -\u003e styleFont, Fill -\u003e styleFill, Border -\u003e styleBorder\n    styleFont,\n    styleFill,\n    styleBorder,\n\n    height,\n    `height=`,\n    writeFile\n\nlet (excel, sheet) = newExcel()\nlet row2 = sheet.row 2\n\n## Let's fill some data.\nrow2[\"D\"] = \"temperian temptest\"\n\n## Now we want to set some style for this particular cell D2.\nrow2.style(\"D\",\n    font = fontStyle(\n        name = \"DejaVu Sans Mono\",\n        size = 11,\n        color = $colBlue, # blue font\n    ),\n    border = borderStyle(\n        top = borderPropStyle(style = bsMedium, color = $colRed),\n        bottom = borderPropStyle(style = bsMediumDashDot, color = $colGreen),\n    ),\n    fill = fillStyle(\n        pattern = patternFillStyle(patternType = ptLightGrid, fgColor = $colRed)\n    ),\n    \n    # Lastly alignment as openarray of pair (string, string),\n    # using openarray for easier to iterate and can be selectively\n    # chosen which style to be applied, other attributes that not recognized\n    # by excel will do nothing.\n    alignment = {\"horizontal\": \"center\", \"vertical\": \"center\",\n        \"wrapText\": $true, \"textRotation\": $45})\n        \n# Libreoffice apparently doesn't support grid filling hence the bgColor is\n# ignored and only read the fgColor.\n# Above we setup the style after putting the value, the reverse is valid too.\n# i.e. set the style and put the value.\n# For setting up font, border and fill, we're using object initializer proc\n# with pattern of \"{objectName}Style\".\n\nrow2.style(\"E\",\n    border = borderStyle(`end` = borderPropStyle(style = bsDashDot, color = $colAzure)),\n    alignment = {\"wrapText\": $true},\n)\nlet longstr = \"brown fox jumps over the lazy dog\".repeat(5).join(\";\")\nrow2[\"E\"] = longstr\n\n# We can also modify set style and change the existing\n\nrow2.style \"D\", alignment = {\"textRotation\": $90}\n\n# here, we changed the alignment style from diagonal direction (45∘)  to upstand (90∘).\n# We can also share a cell style to other cells and since it's shared, any changes to\n# other cells for its styling will affect all others cell it's related.\n\nsheet.shareStyle(\"D2\", \"D4\", \"E4\", \"F4\")\nrow2.shareStyle(\"D\", \"D4\", \"E4\", \"F4\")\n\n# Above, we shares D2 cell style to cells D4, E4, and F4 using two differents\n# proc which work same. This way we can work whether we only have the sheet\n# or we already have the row.\n\n# Another way is to copyStyle, as its named so, we copy style from source cell\n# to target cell(s). This way any changes to any cells it's copied from and to\n# will not affect each others.\n\nsheet.copyStyle(\"D2\", \"D5\", \"E5\", \"F5\")\nrow2.copyStyle(\"D\", \"D5\", \"E5\", \"F5\")\n\n# Apart from sharing and/or copying the style, we can actually fetch the specific\n# setting from cell style.\n\nlet font = row2.styleFont \"D\"\ndoAssert font.name == \"DejaVu Sans Mono\"\ndoAssert font.size == 11\ndoAssert font.color == $colBlue\ndoAssert font.family == -1  # negative value means it will be ignored when applying syle\ndoAssert font.charset == -1 # idem\nlet fill = sheet.styleFill \"D2\"\ndoAssert fill.pattern.fgColor == $colRed\ndoAssert fill.pattern.patternType == ptLightGrid\ndoAssert fill.gradient.stop.color == \"\" # we didn't set the gradient when setting the fillStyle\ndoAssert fill.gradient.stop.position == 0.0\nlet border = sheet.styleBorder \"D2\"\ndoAssert border.top.style == bsMedium\ndoAssert border.top.color == $colRed\ndoAssert border.bottom.style == bsMediumDashDot\ndoAssert border.bottom.color == $colGreen\n\n# Above, we fetch specifically for font, fill, and border using two different (which work same)\n# ways, row proc and sheet proc. Sheet proc underlyingly is using row proc too but both\n# are provided in case we only has row or sheet. The only difference that row only needs to\n# specified the column while the sheet needs to to specify column row cell.\n\n# Now we want to see the cell in its row.\n# Since the cell E2 is quite long and with its alignment has wrapText true, we can also\n# manually change the row height to see all the text.\n\nrow2.height = 200\n\n# By definition, the value is point but it's not clear the relation what's the point points.\n# So if we want to leave how the height for other application to read, we can reset by\n# setting the height 0\n\nrow2.height = 0\n\n# But let's set back to 200 to see how it looks when written to excel file.\n\nrow2.height = 200\ndoAssert row2.height == 200\n\n# And of course we can check what's its set height like above,\n# return 0 if the height reset.\n\nexcel.writeFile \"excelin-example-row-style.xlsx\"\n```\n\nThis is what it looks like when viewed with Libreoffice.\n\n![cell style](assets/cell-style.png)\n\nAnd below is what it looks like when viewed with WPS spreadsheet.\n\n![cell style in wps](assets/cell-style-wps.png)\n\n[Back to examples list](#examples)\n\n## Row display\n\nIn this example, we'll see how to hide, adding the outline level and collapse\nthe row.\n\n```nim\nimport std/with\nimport excelin\n\nlet (excel, sheet) = newExcel()\n\ntemplate hideLevel(rnum, olevel: int): untyped =\n  let r = sheet.row rnum\n  with r:\n    hide = true\n    outlineLevel = olevel\n  r\n    \nsheet.row(2).hide = true\ndiscard 3.hideLevel 3\ndiscard 4.hideLevel 2\ndiscard 5.hideLevel 1\nlet row6 = 6.hideLevel 1\nrow6.collapsed = true\n\nlet row7 = sheet.row 7\nrow7.outlineLevel = 7 # we'll use this to reset the outline below\n\n# Above example we setup 3 rows, row 3 - 5 which each has different\n# outline level decreasing from 3 to 1.\n# For row 6, we set it to be collapsed by setting it true.\n# Let's reset the row 7 outline level by set it to 0.\n\nrow7.outlineLevel = 0\n\nexcel.writeFile \"excelin-example-row-display.xlsx\"\n```\n\n![rows outline collapsing](assets/rows-outline-collapsing.gif)\n\nAs we can see above, the row 2 is hidden with outline level 0 so we can't see it anymore.  \nWhile row 3, 4, 5 has different outline level with row 6 has outline level 1 and it's collapsed.  \nSo we can expand and collapse due different outline level.\n\n[Back to examples list](#examples)\n\n## Sheet auto filter\n\nIn this example, we'll see how to add auto filter by setting ranges to sheet.\n\n```nim\nfrom std/strformat import fmt\nimport excelin\n\n# Let's add a function for easier to populate data in row.\nproc populateRow(row: Row, col, cat: string, data: array[3, float]) =\n  let startcol = col.toNum + 1\n  row[col] = cat\n  var sum = 0.0\n  for i, d in data:\n    row[(startcol+i).toCol] = d\n    sum += d\n  let rnum = row.rowNum\n  let eqrange = fmt\"SUM({col}{rnum}:{(startcol+data.len-1).toCol}{rnum})\" \n  dump eqrange\n  row[(startcol+data.len).toCol] = Formula(equation: eqrange, valueStr: $sum)\n\n# The row data we will work in will be in format\n# D{rnum}: string (Category)\n# E{rnum}: float (Num1)\n# F{rnum}: float (Num2)\n# G{rnum}: float (Num3)\n# H{rnum}: float (Total) with formula SUM(E{rnum}:G{rnum})\n\nlet (excel, sheet) = newExcel()\nlet row5 = sheet.row 5\nlet startcol = \"D\".toNum\nfor i, s in [\"Category\", \"Num1\", \"Num2\", \"Num3\", \"Total\"]:\n  row5[(startcol+i).toCol] = s\n  \n# Above, we set the row 5 starting from D to H i.e. D5:H5  \n# as header for data we will work on.\n\nsheet.row(6).populateRow(\"D\", \"A\", [0.18460660235998017, 0.93463071023892952, 0.58647760893211043])\nsheet.row(7).populateRow(\"D\", \"A\", [0.50425224796279555, 0.25118866081991786, 0.26918159410869791])\nsheet.row(8).populateRow(\"D\", \"A\", [0.6006019062877066, 0.18319235857964333, 0.12254334000604317])\nsheet.row(9).populateRow(\"D\", \"A\", [0.78015011938458589, 0.78159963723670689, 6.7448346870105036E-2])\nsheet.row(10).populateRow(\"D\", \"B\", [0.63608141933645479, 0.35635845012920608, 0.67122053637107193])\nsheet.row(11).populateRow(\"D\", \"B\", [0.33327331908137214, 0.2256497329592122, 0.5793989116090501])\n\nsheet.ranges = (\"D5\", \"H11\")\nsheet.autoFilter = (\"D5\", \"H11\")\n\n# We set the range to sheet by assigning Range which is (top left cell, bottom right cell).\n# Setting up range will not setup the auto filter but setup the auto filter will setup\n# sheet range.\n# We can remove the auto filter by supplying it with empty range (\"\", \"\").\n\n\n# Let's fill the filtering itself.\n\nsheet.filterCol 0, Filter(kind: ftFilter, valuesStr: @[\"A\"])\n\n# Here, we setup simple filter that will be equal to what value it's provided.\n# In above case, we filter the Category column which has value \"A\", and \"B\" so\n# we only get the column that has Category \"A\". We can also the valuesStr with\n# @[\"A\", \"B\"] to get both of data. If there's N data we want to match, supply\n# those all in valuesStr field.\n\n# Below we see another supported filter, custom logic filter\n\nsheet.filterCol 1, Filter(kind: ftCustom, logic: cflAnd,\n  customs: @[(foGt, $0), (foLt, $0.7)])\n\n# here we provide the filter with filter type custom (ftCustom)\n# with its logic \"and\" (cflAnd).\n# In this custom filter, we set the column 1, which Num1 to be\n# value greater than 0 (foGt) and less than 0.7 (foLt)\n\nexcel.writeFile \"excelin-example-autofilter.xlsx\"\n\n```\n\n![resulted sheet auto filter range](assets/sheet-autofilter.png)\n\nAbove is the result from Google sheet.\n\n![resulted sheet auto filter range in wps spreadsheet](assets/sheet-autofilter-wps.png)\n\nAnd this is screenshot when checked with WPS spreadsheet. The difference\nwith Google sheet that in WPS the column 0 (Category) and column 1 (Num1)\nhas different icon because the filtering already defined in those two\ncolumns in our example.\n\n[Back to examples list](#examples)\n\n## Cell merge\n\nFor this example, we'll see how to merge, reset it and also reset style\ninherited from previous merged cells.\n\n```nim\nimport std/colors\nimport excelin\n\nlet (excel, sheet) = newExcel()\n\nlet cellsToMerge = [\n  (\"DejaVu Sans Mono\", 1, \"A\", colGreen, (\"A1\", \"E1\")), # merge in range A1:E1 with font Dejavu Sans Mono\n  (\"Roboto\", 3, \"A\", colBlue, (\"A3\", \"B5\")),\n  (\"Verdana\", 3, \"D\", colRed, (\"D3\", \"E5\")),\n  (\"Consolas\", 7, \"A\", colBlack, (\"A7\", \"E8\"))          # used for deleting merged cells example\n]\n\nfor (fontname, rownum, col, color, `range`) in cellsToMerge:\n  let row = sheet.row rownum\n  row.style(col,\n    #font = fontStyle(name = fontname, size = 13, color = $color),\n    font = fontStyle(name = fontname, size = 13),\n    alignment = {\"horizontal\": \"center\", \"vertical\": \"center\"},\n    fill = fillStyle(\n      pattern = patternFillStyle(patternType = ptLightTrellis, fgColor = $color),\n    ),\n  )\n  row[col] = fontname\n  sheet.mergeCells = `range`\n\n# Let's remove the last merged cells.\nsheet.resetMerge cellsToMerge[^1][4]\n\n# By default, when resetting merge, the existing style (top left cell style)\n# is copied to its subsequent cells in the previous range.\n# We can use resetStyle to remove it.\n\n# The reset merge in range A7:E8, we selectively reset cells.\nsheet.resetStyle(\"B7\", \"D7\", \"A8\", \"C8\", \"E8\")\n\nexcel.writeFile \"excelin-example-merge-cells.xlsx\"\n```\n\nThis is the result when viewed with Libreoffice\n\n![cell merges libreoffice](assets/merge-cells-libreoffice.png)\n\nThis is the result when viewed with WPS\n\n![cell merges wps](assets/merge-cells-wps.png)\n\n[Back to examples list](#examples)\n\n\n## Sheet page breaks\n\nThis example we'll see how to add page break to our sheet.\n\n```nim\nfrom std/strformat import fmt\nimport std/colors\nimport excelin\n\n\nlet (excel, sheet) = newExcel()\n\n# Let's add page break inserted at row 10 with some info\n\nlet row10 = sheet.row 10\nrow10.pageBreak()\nrow10[\"A\"] = \"Above this is the horizontal page break\"\nrow10.style(\"A\",\n  fill = fillStyle(\n    pattern = patternFillStyle(patternType = ptLightGray, fgColor = $colRed),\n  ),\n  alignment = {\"horizontal\": \"center\"},\n)\n\n# For better illustration, we merge cells from column A to A+3 in\n# row 10, the last cell will be merged vertically\n\nsheet.mergeCells = (\"A10\", fmt\"{3.toCol}10\") # remember toCol needs 0-based hence 3 is D\nsheet.mergeCells = (\"E1\", \"E10\")\n\n# Now let's add vertical page break after column E or inserted at column F.\n\nsheet.pageBreakCol(5.toCol)\nlet row1 = sheet.row 1\nrow1[4.toCol] = \"Right this is the vertical page break\"\nrow1.style(\"E\",\n  fill = fillStyle(\n    pattern = patternFillStyle(patternType = ptLightGray, fgColor = $colGreen),\n  ),\n  alignment = {\"vertical\": \"center\", \"wrapText\": $true},\n)\n\nexcel.writeFile \"excelin-example-pagebreak.xlsx\"\n```\n\nThe result as we can see below when viewed with Libreoffice\n\n![page breaks libreoffice](assets/page-breaks-libreoffice.png)\n\n[Back to examples list](#examples)\n\n# Install\n\nExcelin requires minimum Nim version of `v1.4.0`.  \n\nFor installation, we can choose several methods will be mentioned below.\n\nUsing Nimble package (when it's available):\n\n```\nnimble install excelin\n```\n\nOr to install it locally\n\n```\ngit clone https://github.com/mashingan/excelin\ncd excelin\nnimble develop\n```\n\nor directly from Github repo\n\n```\nnimble install https://github.com/mashingan/excelin \n```\n\nto install the `#head` branch\n\n```\nnimble install https://github.com/mashingan/excelin@#head\n#or\nnimble install excelin@#head\n```\n\n# License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashingan%2Fexcelin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmashingan%2Fexcelin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashingan%2Fexcelin/lists"}