forked from sheetjs/docs.sheetjs.com
		
	row-col
This commit is contained in:
		
							parent
							
								
									7a1b75d50b
								
							
						
					
					
						commit
						8e39ab8f33
					
				| @ -37,7 +37,7 @@ The ["Demo"](#demo) creates an app that looks like the screenshots below: | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| ### Integration Details | ||||
| ## Integration Details | ||||
| 
 | ||||
| The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported | ||||
| from the main entrypoint or any script in the project. | ||||
| @ -91,7 +91,7 @@ async function updateFile(v) { try { | ||||
| } catch(e) { console.log(e); } } | ||||
| ``` | ||||
| 
 | ||||
| #### Writing data | ||||
| ### Writing data | ||||
| 
 | ||||
| Starting from an array of objects, the SheetJS `json_to_sheet` method[^5] | ||||
| generates a SheetJS worksheet object. The `book_append_sheet` and `book_new` | ||||
| @ -141,7 +141,7 @@ window.requestFileSystem(window.PERSISTENT, 0, function(fs) { | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ### Demo | ||||
| ## Demo | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
|  | ||||
| @ -179,14 +179,11 @@ ws["!margins"]={left:0.25,right:0.25,top:0.75,bottom:0.75,header:0.3,footer:0.3} | ||||
| 
 | ||||
| In addition to the aforementioned sheet keys, worksheets also add: | ||||
| 
 | ||||
| - `ws['!cols']`: array of column properties objects.  Column widths are actually | ||||
|   stored in files in a normalized manner, measured in terms of the "Maximum | ||||
|   Digit Width" (the largest width of the rendered digits 0-9, in pixels).  When | ||||
|   parsed, the column objects store the pixel width in the `wpx` field, character | ||||
|   width in the `wch` field, and the maximum digit width in the `MDW` field. | ||||
| - `ws['!cols']`: [array of column objects](/docs/csf/features/colprops). | ||||
|   Each column object encodes properties including level, width and visibility. | ||||
| 
 | ||||
| - `ws['!rows']`: array of row properties objects as explained later in the docs. | ||||
|   Each row object encodes properties including row height and visibility. | ||||
| - `ws['!rows']`: [array of row objects](/docs/csf/features/rowprops). | ||||
|   Each row object encodes properties including level, height and visibility. | ||||
| 
 | ||||
| - `ws['!merges']`: array of range objects corresponding to the merged cells in | ||||
|   the worksheet.  Plain text formats do not support merge cells.  CSV export | ||||
|  | ||||
							
								
								
									
										361
									
								
								docz/docs/07-csf/07-features/08-rowprops.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										361
									
								
								docz/docs/07-csf/07-features/08-rowprops.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,361 @@ | ||||
| --- | ||||
| title: Row Properties | ||||
| sidebar_position: 8 | ||||
| --- | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>File Format Support</b> (click to show)</summary> | ||||
| 
 | ||||
| By default, all rows in a workbook are "Visible" and have a standard height. | ||||
| 
 | ||||
| | Formats          | Height | Hidden Rows | Outline Level | | ||||
| |:-----------------|:------:|:-----------:|:-------------:| | ||||
| | XLSX/XLSM        |   ✔    |      ✔      |       ✔       | | ||||
| | XLSB             |   ✔    |      ✔      |       ✔       | | ||||
| | XLML             |   ✔    |      ✔      |       ✕       | | ||||
| | BIFF8 XLS        |   R    |      R      |       R       | | ||||
| | BIFF5 XLS        |   R    |      R      |       R       | | ||||
| | SYLK             |   ✔    |      *      |       ✕       | | ||||
| | ODS / FODS / UOS |   +    |      +      |       +       | | ||||
| 
 | ||||
| Asterisks (*) mark formats that represent hidden rows with zero height. For | ||||
| example, there is no way to specify a custom row height and mark that the row is | ||||
| hidden in the SYLK format. | ||||
| 
 | ||||
| Plus (+) marks formats with limited support. ODS supports specifying row heights | ||||
| in many units of measure. SheetJS supports some but not all ODS units. | ||||
| 
 | ||||
| X (✕) marks features that are not supported by the file formats. For example, | ||||
| the SpreadsheetML 2003 (XLML) file format does not support outline levels. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| Many spreadsheet tools support adjusting row heights to accommodate multiple | ||||
| lines of data or varying text sizes. | ||||
| 
 | ||||
| Some tools additionally support row grouping or "outlining". Excel displays row | ||||
| outline levels to the left of the grid. | ||||
| 
 | ||||
| SheetJS worksheet objects store row properties in the `!rows` field. It is | ||||
| expected to be an array of row metadata objects. | ||||
| 
 | ||||
| ## Demo | ||||
| 
 | ||||
| This example creates a workbook that includes custom row heights, hidden rows, | ||||
| and row outline levels. | ||||
| 
 | ||||
| <table><thead><tr> | ||||
|   <th>Excel for Windows</th> | ||||
|   <th>Excel for Mac</th> | ||||
| </tr></thead><tbody><tr><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| </td><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| <details><summary><b>Export Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| The table lists the assigned heights, outline levels and visibility settings. | ||||
| 
 | ||||
| ```jsx live | ||||
| function SheetJSRowProps() { | ||||
|   const [ws, setWS] = React.useState(); | ||||
|   const [__html, setHTML] = React.useState(""); | ||||
|   const fmt = React.useRef(null); | ||||
| 
 | ||||
|   /* when the page is loaded, create worksheet and show table */ | ||||
|   React.useEffect(() => { | ||||
|     /* Create worksheet from simple data */ | ||||
|     const data = [ | ||||
|       { Height: 20, Unit: "px", Level: 0 }, { Height: 25, Unit: "pt", Level: 1 }, | ||||
|       { Height: 30, Unit: "px", Level: 2 }, { Height: 35, Unit: "pt", Level: 3 }, | ||||
|       { Height: 25, Unit: "pt", Level: 3 }, { Height: 15, Unit: "px", Level: 1 }, | ||||
|       { Height: 10, Unit: "pt", Level: 0 }, { Hidden: true } | ||||
|     ]; | ||||
|     const ws = XLSX.utils.json_to_sheet(data); | ||||
|     /* set row metadata */ | ||||
|     ws["!rows"] = []; | ||||
|     data.forEach((row, i) => { | ||||
|       const r = {}; | ||||
|       if(row.Level) (ws["!rows"][i+1] = r).level = row.Level; | ||||
|       if(row.Unit == "px") (ws["!rows"][i+1] = r).hpx = row.Height || 0; | ||||
|       if(row.Unit == "pt") (ws["!rows"][i+1] = r).hpt = row.Height || 0; | ||||
|       if(row.Hidden) (ws["!rows"][i+1] = r).hidden = true; | ||||
|     }); | ||||
| 
 | ||||
|     /* save worksheet object for the export */ | ||||
|     setWS(ws); | ||||
|     /* generate the HTML table */ | ||||
|     setHTML(XLSX.utils.sheet_to_html(ws)); | ||||
|   }, []); | ||||
| 
 | ||||
|   const xport = (fmt) => { | ||||
|     /* Export to file (start a download) */ | ||||
|     const wb = XLSX.utils.book_new(); | ||||
|     XLSX.utils.book_append_sheet(wb, ws, "Formats"); | ||||
|     XLSX.writeFile(wb, `SheetJSRowProps.${fmt}`, {cellStyles: true}); | ||||
|   }; | ||||
| 
 | ||||
|   const fmts = ["xlsx", "xlsb", "xls", "slk", "ods"]; | ||||
|   return ( <> | ||||
|     <b>File format: </b> | ||||
|     <select ref={fmt}>{fmts.map(f=>(<option value={f}>{f}</option>))}</select> | ||||
|     <br/><button onClick={()=>xport(fmt.current.value)}><b>Export!</b></button> | ||||
|     <div dangerouslySetInnerHTML={{__html}}/> | ||||
|   </> ); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ## Functions | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| **Row processing must be explicitly enabled!** | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| Functions creating worksheet objects are not guaranteed to generate the `!rows` | ||||
| array. Writers are not guaranteed to export row metadata. | ||||
| 
 | ||||
| #### Reading Files | ||||
| 
 | ||||
| [`read` and `readFile`](/docs/api/parse-options) accept an options argument. The | ||||
| `cellStyles` option must be set to `true` to generate row properties: | ||||
| 
 | ||||
| ```js | ||||
| var wb = XLSX.read(data, {/* ... other options , */ cellStyles: true}); | ||||
| ``` | ||||
| 
 | ||||
| #### Writing Files | ||||
| 
 | ||||
| [`write` and `writeFile`](/docs/api/write-options) accept an options argument. | ||||
| The `cellStyles` option must be set to `true` to export row properties: | ||||
| 
 | ||||
| ```js | ||||
| XLSX.writeFile(wb, "SheetJSRowProps.xlsx", {/* ...opts , */ cellStyles: true}); | ||||
| ``` | ||||
| 
 | ||||
| #### Importing HTML Tables | ||||
| 
 | ||||
| [`table_to_book` and `table_to_sheet`](/docs/api/utilities/html#html-table-input) | ||||
| process HTML DOM TABLE elements. | ||||
| 
 | ||||
| Individual table rows (`TR` elements) can be marked as hidden by setting the CSS | ||||
| `display` property to `none`. | ||||
| 
 | ||||
| By default, hidden rows are imported and appropriately marked as hidden: | ||||
| 
 | ||||
| ```js | ||||
| /* generate worksheet from first table, preserving hidden rows */ | ||||
| var tbl = document.getElementsByTagName("TABLE")[0]; | ||||
| var ws = XLSX.utils.table_to_sheet(tbl); | ||||
| ``` | ||||
| 
 | ||||
| If the `display` option is set to `true`, hidden rows will be skipped: | ||||
| 
 | ||||
| ```js | ||||
| /* generate worksheet from first table, omitting hidden rows */ | ||||
| var tbl = document.getElementsByTagName("TABLE")[0]; | ||||
| var ws = XLSX.utils.table_to_sheet(tbl, {display: true}) | ||||
| ``` | ||||
| 
 | ||||
| #### Exporting Data | ||||
| 
 | ||||
| [`sheet_to_csv`](/docs/api/utilities/csv#delimiter-separated-output) and | ||||
| [`sheet_to_json`](/docs/api/utilities/array#array-output) accept options. If the | ||||
| `skipHidden` option is set to true, hidden rows will not be exported: | ||||
| 
 | ||||
| ```js | ||||
| var ws = wb.Sheets[wb.SheetNames[0]]; // first worksheet | ||||
| var csv = XLSX.utils.sheet_to_csv(ws, {/* ...opts, */ skipHidden: true}); | ||||
| ``` | ||||
| 
 | ||||
| ## Storage | ||||
| 
 | ||||
| The `!rows` property in a sheet object stores row-level metadata. If present, it | ||||
| is expected to be an array of row objects. | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| As explained in ["Addresses and Ranges"](/docs/csf/general#rows), SheetJS uses | ||||
| zero-indexed rows. The row metadata for Excel row 20 is stored at index 19 of | ||||
| the `!rows` array. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| When performing operations, it is strongly recommended to test for the existence | ||||
| of the row structure. | ||||
| 
 | ||||
| This snippet checks the `!rows` array and the specific row object, creating them | ||||
| if they do not exist, before setting the `hidden` property of the third row: | ||||
| 
 | ||||
| ```js | ||||
| /* Excel third row -> SheetJS row index 3 - 1 = 2 */ | ||||
| var ROW_INDEX = 2; | ||||
| 
 | ||||
| /* create !rows array if it does not exist */ | ||||
| if(!ws["!rows"]) ws["!rows"] = []; | ||||
| 
 | ||||
| /* create row metadata object if it does not exist */ | ||||
| if(!ws["!rows"][ROW_INDEX]) ws["!rows"][ROW_INDEX] = {hpx: 20}; | ||||
| 
 | ||||
| /* set row to hidden */ | ||||
| ws["!rows"][ROW_INDEX].hidden = true; | ||||
| ``` | ||||
| 
 | ||||
| ### Row Heights | ||||
| 
 | ||||
| Row heights can be specified in two ways: | ||||
| 
 | ||||
| | Property | Description             | | ||||
| |:---------|:------------------------| | ||||
| | `hpx`    | Height in screen pixels | | ||||
| | `hpt`    | Height in points        | | ||||
| 
 | ||||
| The following snippet sets the height of the third row to 50 pixels: | ||||
| 
 | ||||
| ```js | ||||
| const ROW_HEIGHT = 50; | ||||
| 
 | ||||
| /* Excel third row -> SheetJS row index 3 - 1 = 2 */ | ||||
| const ROW_INDEX = 2; | ||||
| 
 | ||||
| /* create !rows array if it does not exist */ | ||||
| if(!ws["!rows"]) ws["!rows"] = []; | ||||
| 
 | ||||
| /* create row metadata object if it does not exist */ | ||||
| if(!ws["!rows"][ROW_INDEX]) ws["!rows"][ROW_INDEX] = {hpx: ROW_HEIGHT}; | ||||
| 
 | ||||
| /* set row height */ | ||||
| ws["!rows"][ROW_INDEX].hpx = ROW_HEIGHT; | ||||
| ``` | ||||
| 
 | ||||
| ### Row Visibility | ||||
| 
 | ||||
| The `hidden` property controls visibility. | ||||
| 
 | ||||
| The following snippet hides the fourth row: | ||||
| 
 | ||||
| ```js | ||||
| /* Excel fourth row -> SheetJS row index 4 - 1 = 3 */ | ||||
| var ROW_INDEX = 3; | ||||
| 
 | ||||
| /* create !rows array if it does not exist */ | ||||
| if(!ws["!rows"]) ws["!rows"] = []; | ||||
| 
 | ||||
| /* create row metadata object if it does not exist */ | ||||
| if(!ws["!rows"][ROW_INDEX]) ws["!rows"][ROW_INDEX] = {hpx: 20}; | ||||
| 
 | ||||
| /* set row to hidden */ | ||||
| ws["!rows"][ROW_INDEX].hidden = true; | ||||
| ``` | ||||
| 
 | ||||
| ### Outline Levels | ||||
| 
 | ||||
| The `level` property controls outline level / grouping. It is expected to be a | ||||
| number between `0` and `7` inclusive. | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| The Excel UI displays outline levels next to the column labels. The base level | ||||
| shown in the application is `1`. | ||||
| 
 | ||||
| SheetJS is zero-indexed: the default (base) level is `0`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The following snippet sets the level of the sixth row to Excel 2 / SheetJS 1: | ||||
| 
 | ||||
| ```js | ||||
| /* Excel level 2 -> SheetJS level 2 - 1 = 1 */ | ||||
| var LEVEL = 1; | ||||
| 
 | ||||
| /* Excel sixth row -> SheetJS row index 6 - 1 = 5 */ | ||||
| var ROW_INDEX = 2; | ||||
| 
 | ||||
| /* create !rows array if it does not exist */ | ||||
| if(!ws["!rows"]) ws["!rows"] = []; | ||||
| 
 | ||||
| /* create row metadata object if it does not exist */ | ||||
| if(!ws["!rows"][ROW_INDEX]) ws["!rows"][ROW_INDEX] = {hpx: 20}; | ||||
| 
 | ||||
| /* set level */ | ||||
| ws["!rows"][ROW_INDEX].level = LEVEL; | ||||
| ``` | ||||
| 
 | ||||
| ### Grouping Rows | ||||
| 
 | ||||
| Applications treat consecutive rows with the same level as part of a "group". | ||||
| 
 | ||||
| The "Group" command typically increments the level of each row in the range: | ||||
| 
 | ||||
| ```js | ||||
| /* start_row and end_row are SheetJS 0-indexed row indices */ | ||||
| function gruppieren(ws, start_row, end_row) { | ||||
|   /* create !rows array if it does not exist */ | ||||
|   if(!ws["!rows"]) ws["!rows"] = []; | ||||
|   /* loop over every row index */ | ||||
|   for(var i = start_row; i <= end_row; ++i) { | ||||
|     /* create row metadata object if it does not exist */ | ||||
|     if(!ws["!rows"][i]) ws["!rows"][i] = {hpx: 20}; | ||||
|     /* increment level */ | ||||
|     ws["!rows"][i].level = 1 + (ws["!rows"][i].level || 0); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| The "Ungroup" command typically decrements the level of each row in the range: | ||||
| 
 | ||||
| ```js | ||||
| /* start_row and end_row are SheetJS 0-indexed row indices */ | ||||
| function dissocier(ws, start_row, end_row) { | ||||
|   /* create !rows array if it does not exist */ | ||||
|   if(!ws["!rows"]) ws["!rows"] = []; | ||||
|   /* loop over every row index */ | ||||
|   for(var i = start_row; i <= end_row; ++i) { | ||||
|     /* if row metadata does not exist, the level is zero -> skip */ | ||||
|     if(!ws["!rows"][i]) continue; | ||||
|     /* if row level is not specified, the level is zero -> skip */ | ||||
|     if(!ws["!rows"][i].level) continue; | ||||
|     /* decrement level */ | ||||
|     --ws["!rows"][i].level; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Grouping Symbol | ||||
| 
 | ||||
| By default, Excel displays the group collapse button on the row after the data. | ||||
| In the UI, this is adjusted by the option "Summary rows below detail". | ||||
| 
 | ||||
| SheetJS exposes this option in the `above` property of the `"!outline"` property | ||||
| of worksheet objects. Setting this property to `true` effectively "unchecks" the | ||||
| "Summary rows below detail" option in Excel: | ||||
| 
 | ||||
| ```js | ||||
| if(!ws["outline"]) ws["!outline"] = {}; | ||||
| ws["!outline"].above = true; // show summary rows above detail | ||||
| ``` | ||||
| 
 | ||||
| ## Implementation Details | ||||
| 
 | ||||
| <details><summary><b>Details</b> (click to show)</summary> | ||||
| 
 | ||||
| Excel internally stores row heights in points.  The default resolution is 72 DPI | ||||
| or 96 PPI, so the pixel and point size should agree.  For different resolutions | ||||
| they may not agree, so the library separates the concepts. | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `hpx` pixel height if available | ||||
| 
 | ||||
| 2) use `hpt` point height if available | ||||
| 
 | ||||
| </details> | ||||
							
								
								
									
										418
									
								
								docz/docs/07-csf/07-features/09-colprops.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										418
									
								
								docz/docs/07-csf/07-features/09-colprops.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,418 @@ | ||||
| --- | ||||
| title: Column Properties | ||||
| sidebar_position: 9 | ||||
| --- | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>File Format Support</b> (click to show)</summary> | ||||
| 
 | ||||
| By default, all columns in a workbook are "Visible" and have a standard width. | ||||
| 
 | ||||
| | Formats          | Width | Hidden Cols | Outline Level | | ||||
| |:-----------------|:-----:|:-----------:|:-------------:| | ||||
| | XLSX/XLSM        |   ✔   |      ✔      |       ✔       | | ||||
| | XLSB             |   ✔   |      ✔      |       ✔       | | ||||
| | XLML             |   ✔   |      ✔      |       ✕       | | ||||
| | BIFF8 XLS        |   ✔   |      ✔      |       ✔       | | ||||
| | BIFF5 XLS        |   R   |      R      |       R       | | ||||
| | SYLK             |   ✔   |      *      |       ✕       | | ||||
| 
 | ||||
| Asterisks (*) mark formats that represent hidden columns with zero width. For | ||||
| example, there is no way to specify a custom column width and mark the column as | ||||
| hidden in the SYLK format. | ||||
| 
 | ||||
| X (✕) marks features that are not supported by the file formats. For example, | ||||
| the SpreadsheetML 2003 (XLML) file format does not support outline levels. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| Many spreadsheet tools support adjusting column widths to accommodate longer | ||||
| formatted data or varying text sizes. | ||||
| 
 | ||||
| Some tools additionally support column grouping or "outlining". Excel displays | ||||
| outline levels above the grid. | ||||
| 
 | ||||
| SheetJS worksheet objects store column properties in the `!cols` field. It is | ||||
| expected to be an array of column metadata objects. | ||||
| 
 | ||||
| :::warning Excel Bugs | ||||
| 
 | ||||
| For most common formats (XLSX, XLS), widths are tied to font metrics, which are | ||||
| tied to Windows Scaling settings. In Windows 11, the Scale factor settings are | ||||
| found in "System" > "Display" > "Scale" | ||||
| 
 | ||||
| **Column widths may appear different on other machines due to scaling.** | ||||
| 
 | ||||
| **This is an issue with Excel.** | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Demo | ||||
| 
 | ||||
| This example creates a workbook that includes custom column widths, hidden | ||||
| columns, and column outline levels. | ||||
| 
 | ||||
| <table><thead><tr> | ||||
|   <th>Excel for Windows</th> | ||||
|   <th>Excel for Mac</th> | ||||
| </tr></thead><tbody><tr><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| </td><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| <details><summary><b>Export Demo</b> (click to show)</summary> | ||||
| 
 | ||||
| The table lists the assigned widths, outline levels and visibility settings. | ||||
| 
 | ||||
| ```jsx live | ||||
| function SheetJColProps() { | ||||
|   const [ws, setWS] = React.useState(); | ||||
|   const [__html, setHTML] = React.useState(""); | ||||
|   const fmt = React.useRef(null); | ||||
| 
 | ||||
|   /* when the page is loaded, create worksheet and show table */ | ||||
|   React.useEffect(() => { | ||||
|     /* Create worksheet from simple data */ | ||||
|     const data = [ | ||||
|       [ "Width"  , 10, 20, 30, 40, 50, 20, 20,   ], | ||||
|       [ "Level"  ,  0,  1,  2,  3,  3,  1,  0,   ], | ||||
|       [ "Hidden" ,  0,  0,  0,  0,  0,  0,  0, 1 ] | ||||
|     ]; | ||||
|     const ws = XLSX.utils.aoa_to_sheet(data); | ||||
|     /* set column metadata */ | ||||
|     ws["!cols"] = []; | ||||
|     for(let i = 1; i <= 8; ++i) { | ||||
|       const r = {}; | ||||
|       if(data[0][i] != null) (ws["!cols"][i] = r).wpx = data[0][i]; | ||||
|       if(data[1][i] != null) (ws["!cols"][i] = r).level = data[1][i]; | ||||
|       if(data[2][i] != null) (ws["!cols"][i] = r).hidden = data[2][i]; | ||||
|     } | ||||
| 
 | ||||
|     /* save worksheet object for the export */ | ||||
|     setWS(ws); | ||||
|     /* generate the HTML table */ | ||||
|     setHTML(XLSX.utils.sheet_to_html(ws)); | ||||
|   }, []); | ||||
| 
 | ||||
|   const xport = (fmt) => { | ||||
|     /* Export to file (start a download) */ | ||||
|     const wb = XLSX.utils.book_new(); | ||||
|     XLSX.utils.book_append_sheet(wb, ws, "Formats"); | ||||
|     XLSX.writeFile(wb, `SheetJSColProps.${fmt}`, {cellStyles: true}); | ||||
|   }; | ||||
| 
 | ||||
|   const fmts = ["xlsx", "xlsb", "xls", "slk"]; | ||||
|   return ( <> | ||||
|     <b>File format: </b> | ||||
|     <select ref={fmt}>{fmts.map(f=>(<option value={f}>{f}</option>))}</select> | ||||
|     <br/><button onClick={()=>xport(fmt.current.value)}><b>Export!</b></button> | ||||
|     <div dangerouslySetInnerHTML={{__html}}/> | ||||
|   </> ); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ## Functions | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| **Column processing must be explicitly enabled!** | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| Functions creating worksheet objects are not guaranteed to generate the `!cols` | ||||
| array. Writers are not guaranteed to export column metadata. | ||||
| 
 | ||||
| #### Reading Files | ||||
| 
 | ||||
| [`read` and `readFile`](/docs/api/parse-options) accept an options argument. The | ||||
| `cellStyles` option must be set to `true` to generate column properties: | ||||
| 
 | ||||
| ```js | ||||
| var wb = XLSX.read(data, {/* ... other options , */ cellStyles: true}); | ||||
| ``` | ||||
| 
 | ||||
| #### Writing Files | ||||
| 
 | ||||
| [`write` and `writeFile`](/docs/api/write-options) accept an options argument. | ||||
| The `cellStyles` option must be set to `true` to export column properties: | ||||
| 
 | ||||
| ```js | ||||
| XLSX.writeFile(wb, "SheetSColProps.xlsx", {/* ...opts , */ cellStyles: true}); | ||||
| ``` | ||||
| 
 | ||||
| #### Exporting Data | ||||
| 
 | ||||
| [`sheet_to_csv`](/docs/api/utilities/csv#delimiter-separated-output) and | ||||
| [`sheet_to_json`](/docs/api/utilities/array#array-output) accept options. If the | ||||
| `skipHidden` option is set to true, hidden columns will not be exported: | ||||
| 
 | ||||
| ```js | ||||
| var ws = wb.Sheets[wb.SheetNames[0]]; // first worksheet | ||||
| var csv = XLSX.utils.sheet_to_csv(ws, {/* ...opts, */ skipHidden: true}); | ||||
| ``` | ||||
| 
 | ||||
| ## Storage | ||||
| 
 | ||||
| The `!cols` property in a sheet object stores column-level metadata. If present, | ||||
| it is expected to be an array of column objects. | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| As explained in ["Addresses and Ranges"](/docs/csf/general#columns), SheetJS uses | ||||
| zero-indexed columns. The column metadata for Excel column "T" is stored at index | ||||
| 19 of the `!cols` array. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| When performing operations, it is strongly recommended to test for the existence | ||||
| of the column structure. | ||||
| 
 | ||||
| This snippet checks the `!cols` array and the specific column object, creating | ||||
| them if they do not exist, before setting the `hidden` property of column "C": | ||||
| 
 | ||||
| ```js | ||||
| /* Excel column "C" -> SheetJS column index 2 == XLSX.utils.decode_col("C") */ | ||||
| var COL_INDEX = 2; | ||||
| 
 | ||||
| /* create !cols array if it does not exist */ | ||||
| if(!ws["!cols"]) ws["!cols"] = []; | ||||
| 
 | ||||
| /* create column metadata object if it does not exist */ | ||||
| if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8}; | ||||
| 
 | ||||
| /* set column to hidden */ | ||||
| ws["!cols"][COL_INDEX].hidden = true; | ||||
| ``` | ||||
| 
 | ||||
| ### Column Widths | ||||
| 
 | ||||
| Column widths can be specified in three ways: | ||||
| 
 | ||||
| | Property | Description             | Excel UI | | ||||
| |:---------|:------------------------|:---------| | ||||
| | `wpx`    | Width in screen pixels  | Pixels   | | ||||
| | `wch`    | "inner width" in MDW ** | Width    | | ||||
| | `width`  | "outer width" in MDW ** |          | | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| When resizing a column, Excel will show a tooltip: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| `wpx` stores the "pixels" field (`65` in the diagram) for certain computer and | ||||
| font settings. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| <details><summary><b>MDW (Max Digit Width)</b> (click to show)</summary> | ||||
| 
 | ||||
| **`MDW`** | ||||
| 
 | ||||
| "MDW" stands for "Max Digit Width", the maximum width of the numeric characters | ||||
| (`0`, `1`, ..., `9`) using the first font specified in the file. For most common | ||||
| fonts and text scaling settings, this is the width of `0` measured in pixels. | ||||
| 
 | ||||
| Parsers will save the estimated pixel width of the `0` digit to the `MDW` | ||||
| property of the column object. It is always a positive integer. | ||||
| 
 | ||||
| **`width`** | ||||
| 
 | ||||
| `width` is the distance from "gridline before the current column" to "gridline | ||||
| before the next column" divided by MDW and rounded to the nearest `1/256`. | ||||
| 
 | ||||
| **`wch`** | ||||
| 
 | ||||
| Table cells in Excel include 2 pixels of padding on each side. The vertical | ||||
| gridline is one pixel wide. In total, the `width` includes 5 pixels of padding. | ||||
| 
 | ||||
| `wch` is the "inner width", calculated by subtracting the 5 pixels from `width`. | ||||
| `wch` is also measured in MDW units rounded to the nearest `1/256`. | ||||
| 
 | ||||
| **Diagram** | ||||
| 
 | ||||
| The following diagram depicts the Excel box model and the relationship between | ||||
| `width`, `wpx`, `MDW` and the displayed grid: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| The distance between the two red lines is `width * MDW = 15` pixels. That span | ||||
| includes one gridline width (1 pixel) and two padding blocks (2 pixels each). | ||||
| 
 | ||||
| The space available for content is `wch * MDW = 15 - 5 = 10` pixels. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| The following snippet sets the width of column "C" to 50 pixels: | ||||
| 
 | ||||
| ```js | ||||
| const COL_WIDTH = 50; | ||||
| 
 | ||||
| /* Excel column "C" -> SheetJS column index 2 == XLSX.utils.decode_col("C") */ | ||||
| var COL_INDEX = 2; | ||||
| 
 | ||||
| /* create !cols array if it does not exist */ | ||||
| if(!ws["!cols"]) ws["!cols"] = []; | ||||
| 
 | ||||
| /* create column metadata object if it does not exist */ | ||||
| if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8}; | ||||
| 
 | ||||
| /* set column width */ | ||||
| ws["!cols"][COL_INDEX].wpx = COL_WIDTH; | ||||
| ``` | ||||
| 
 | ||||
| ### Column Visibility | ||||
| 
 | ||||
| The `hidden` property controls visibility. | ||||
| 
 | ||||
| The following snippet hides column "D": | ||||
| 
 | ||||
| ```js | ||||
| /* Excel column "D" -> SheetJS column index 3 == XLSX.utils.decode_col("D") */ | ||||
| var COL_INDEX = 3; | ||||
| 
 | ||||
| /* create !cols array if it does not exist */ | ||||
| if(!ws["!cols"]) ws["!cols"] = []; | ||||
| 
 | ||||
| /* create column metadata object if it does not exist */ | ||||
| if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8}; | ||||
| 
 | ||||
| /* set column to hidden */ | ||||
| ws["!cols"][COL_INDEX].hidden = true; | ||||
| ``` | ||||
| 
 | ||||
| ### Outline Levels | ||||
| 
 | ||||
| The `level` property controls outline level / grouping. It is expected to be a | ||||
| number between `0` and `7` inclusive. | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| The Excel UI displays outline levels above the row labels. The base level | ||||
| shown in the application is `1`. | ||||
| 
 | ||||
| SheetJS is zero-indexed: the default (base) level is `0`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The following snippet sets the level of column "F" to Excel 2 / SheetJS 1: | ||||
| 
 | ||||
| ```js | ||||
| /* Excel level 2 -> SheetJS level 2 - 1 = 1 */ | ||||
| var LEVEL = 1; | ||||
| 
 | ||||
| /* Excel column "F" -> SheetJS column index 5 == XLSX.utils.decode_col("F") */ | ||||
| var COL_INDEX = 5; | ||||
| 
 | ||||
| /* create !cols array if it does not exist */ | ||||
| if(!ws["!cols"]) ws["!cols"] = []; | ||||
| 
 | ||||
| /* create column metadata object if it does not exist */ | ||||
| if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8}; | ||||
| 
 | ||||
| /* set level */ | ||||
| ws["!cols"][COL_INDEX].level = LEVEL; | ||||
| ``` | ||||
| 
 | ||||
| ### Grouping Columns | ||||
| 
 | ||||
| Applications treat consecutive columns with the same level as part of a "group". | ||||
| 
 | ||||
| The "Group" command typically increments the level of each column in the range: | ||||
| 
 | ||||
| ```js | ||||
| /* start_col and end_col are SheetJS 0-indexed column indices */ | ||||
| function grouper(ws, start_col, end_col) { | ||||
|   /* create !cols array if it does not exist */ | ||||
|   if(!ws["!cols"]) ws["!cols"] = []; | ||||
|   /* loop over every column index */ | ||||
|   for(var i = start_col; i <= end_col; ++i) { | ||||
|     /* create column metadata object if it does not exist */ | ||||
|     if(!ws["!cols"][i]) ws["!cols"][i] = {wch: 8}; | ||||
|     /* increment level */ | ||||
|     ws["!cols"][i].level = 1 + (ws["!cols"][i].level || 0); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| The "Ungroup" command typically decrements the level of each column in the range: | ||||
| 
 | ||||
| ```js | ||||
| /* start_col and end_col are SheetJS 0-indexed column indices */ | ||||
| function aufheben(ws, start_col, end_col) { | ||||
|   /* create !cols array if it does not exist */ | ||||
|   if(!ws["!cols"]) ws["!cols"] = []; | ||||
|   /* loop over every column index */ | ||||
|   for(var i = start_col; i <= end_col; ++i) { | ||||
|     /* if column metadata does not exist, the level is zero -> skip */ | ||||
|     if(!ws["!cols"][i]) continue; | ||||
|     /* if column level is not specified, the level is zero -> skip */ | ||||
|     if(!ws["!cols"][i].level) continue; | ||||
|     /* decrement level */ | ||||
|     --ws["!cols"][i].level; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Grouping Symbol | ||||
| 
 | ||||
| By default, Excel displays the group collapse button on the column after the | ||||
| data. In the UI, this option is named "Summary columns to right of detail". | ||||
| 
 | ||||
| SheetJS exposes this option in the `left` property of the `"!outline"` property | ||||
| of worksheet objects. Setting this property to `true` effectively "unchecks" the | ||||
| "Summary columns to right of detail" option in Excel: | ||||
| 
 | ||||
| ```js | ||||
| if(!ws["outline"]) ws["!outline"] = {}; | ||||
| ws["!outline"].left = true; // show summary to left of detail | ||||
| ``` | ||||
| 
 | ||||
| ## Implementation Details | ||||
| 
 | ||||
| <details><summary><b>Details</b> (click to show)</summary> | ||||
| 
 | ||||
| **Three Width Types** | ||||
| 
 | ||||
| There are three different width types corresponding to the three different ways | ||||
| spreadsheets store column widths: | ||||
| 
 | ||||
| SYLK and other plain text formats use raw character count. Contemporaneous tools | ||||
| like Visicalc and Multiplan were character based.  Since the characters had the | ||||
| same width, it sufficed to store a count.  This tradition was continued into the | ||||
| BIFF formats. | ||||
| 
 | ||||
| SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel | ||||
| count throughout the file.  Column widths, row heights, and other measures use | ||||
| pixels.  When the pixel and character counts do not align, Excel rounds values. | ||||
| 
 | ||||
| XLSX internally stores column widths in a nebulous "Max Digit Width" form.  The | ||||
| Max Digit Width is the width of the largest digit when rendered (generally the | ||||
| "0" character is the widest).  The internal width must be an integer multiple of | ||||
| the width divided by 256.  ECMA-376 describes a formula for converting between | ||||
| pixels and the internal width.  This represents a hybrid approach. | ||||
| 
 | ||||
| Read functions attempt to populate all three properties.  Write functions will | ||||
| try to cycle specified values to the desired type.  In order to avoid potential | ||||
| conflicts, manipulation should delete the other properties first.  For example, | ||||
| when changing the pixel width, delete the `wch` and `width` properties. | ||||
| 
 | ||||
| **Column Width Priority** | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `width` field if available | ||||
| 
 | ||||
| 2) use `wpx` pixel width if available | ||||
| 
 | ||||
| 3) use `wch` character count if available | ||||
| 
 | ||||
| </details> | ||||
| @ -17,124 +17,3 @@ The following topics are covered in sub-pages: | ||||
|     <a href={item.href}>{item.label}</a>{cP?.summary && (" - " + cP.summary)} | ||||
|   </li> ); | ||||
| })}</ul> | ||||
| 
 | ||||
| ## Row and Column Properties | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>Format Support</b> (click to show)</summary> | ||||
| 
 | ||||
| **Row Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM, ODS | ||||
| 
 | ||||
| **Column Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 
 | ||||
| Row and Column properties are not extracted by default when reading from a file | ||||
| and are not persisted by default when writing to a file. The option | ||||
| `cellStyles: true` must be passed to the relevant read or write function. | ||||
| 
 | ||||
| _Column Properties_ | ||||
| 
 | ||||
| The `!cols` array in each worksheet, if present, is a collection of `ColInfo` | ||||
| objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type ColInfo = { | ||||
|   /* visibility */ | ||||
|   hidden?: boolean; // if true, the column is hidden | ||||
| 
 | ||||
|   /* column width is specified in one of the following ways: */ | ||||
|   wpx?:    number;  // width in screen pixels | ||||
|   width?:  number;  // width in Excel "Max Digit Width", width*256 is integral | ||||
|   wch?:    number;  // width in characters | ||||
| 
 | ||||
|   /* other fields for preserving features from files */ | ||||
|   level?:  number;  // 0-indexed outline / group level | ||||
|   MDW?:    number;  // Excel "Max Digit Width" unit, always integral | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| _Row Properties_ | ||||
| 
 | ||||
| The `!rows` array in each worksheet, if present, is a collection of `RowInfo` | ||||
| objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type RowInfo = { | ||||
|   /* visibility */ | ||||
|   hidden?: boolean; // if true, the row is hidden | ||||
| 
 | ||||
|   /* row height is specified in one of the following ways: */ | ||||
|   hpx?:    number;  // height in screen pixels | ||||
|   hpt?:    number;  // height in points | ||||
| 
 | ||||
|   level?:  number;  // 0-indexed outline / group level | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| _Outline / Group Levels Convention_ | ||||
| 
 | ||||
| The Excel UI displays the base outline level as `1` and the max level as `8`. | ||||
| Following JS conventions, SheetJS uses 0-indexed outline levels wherein the base | ||||
| outline level is `0` and the max level is `7`. | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>Why are there three width types?</b> (click to show)</summary> | ||||
| 
 | ||||
| There are three different width types corresponding to the three different ways | ||||
| spreadsheets store column widths: | ||||
| 
 | ||||
| SYLK and other plain text formats use raw character count. Contemporaneous tools | ||||
| like Visicalc and Multiplan were character based.  Since the characters had the | ||||
| same width, it sufficed to store a count.  This tradition was continued into the | ||||
| BIFF formats. | ||||
| 
 | ||||
| SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel | ||||
| count throughout the file.  Column widths, row heights, and other measures use | ||||
| pixels.  When the pixel and character counts do not align, Excel rounds values. | ||||
| 
 | ||||
| XLSX internally stores column widths in a nebulous "Max Digit Width" form.  The | ||||
| Max Digit Width is the width of the largest digit when rendered (generally the | ||||
| "0" character is the widest).  The internal width must be an integer multiple of | ||||
| the width divided by 256.  ECMA-376 describes a formula for converting between | ||||
| pixels and the internal width.  This represents a hybrid approach. | ||||
| 
 | ||||
| Read functions attempt to populate all three properties.  Write functions will | ||||
| try to cycle specified values to the desired type.  In order to avoid potential | ||||
| conflicts, manipulation should delete the other properties first.  For example, | ||||
| when changing the pixel width, delete the `wch` and `width` properties. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>Implementation details</b> (click to show)</summary> | ||||
| 
 | ||||
| _Row Heights_ | ||||
| 
 | ||||
| Excel internally stores row heights in points.  The default resolution is 72 DPI | ||||
| or 96 PPI, so the pixel and point size should agree.  For different resolutions | ||||
| they may not agree, so the library separates the concepts. | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `hpx` pixel height if available | ||||
| 2) use `hpt` point height if available | ||||
| 
 | ||||
| _Column Widths_ | ||||
| 
 | ||||
| Given the constraints, it is possible to determine the `MDW` without actually | ||||
| inspecting the font!  The parsers guess the pixel width by converting from width | ||||
| to pixels and back, repeating for all possible `MDW` and selecting the value | ||||
| that minimizes the error.  XLML actually stores the pixel width, so the guess | ||||
| works in the opposite direction. | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `width` field if available | ||||
| 2) use `wpx` pixel width if available | ||||
| 3) use `wch` character count if available | ||||
| 
 | ||||
| </details> | ||||
|  | ||||
| @ -141,6 +141,7 @@ The function takes an options argument: | ||||
| |`sheetStubs` |  false  | Create cell objects of type `z` for `null` values    | | ||||
| |`nullError`  |  false  | If true, emit `#NULL!` error cells for `null` values | | ||||
| |`UTC`        |  false  | If true, dates are interpreted using UTC methods **  | | ||||
| |`dense`      |  false  | Emit [dense sheet object](docs/csf/sheet#dense-mode) | | ||||
| 
 | ||||
| [UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option) | ||||
| 
 | ||||
| @ -279,6 +280,7 @@ default column order is determined by the first appearance of the field using | ||||
| |`skipHeader` |  false  | If true, do not include header row in output         | | ||||
| |`nullError`  |  false  | If true, emit `#NULL!` error cells for `null` values | | ||||
| |`UTC`        |  false  | If true, dates are interpreted using UTC methods **  | | ||||
| |`dense`      |  false  | Emit [dense sheet object](docs/csf/sheet#dense-mode) | | ||||
| 
 | ||||
| [UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option) | ||||
| 
 | ||||
| @ -524,6 +526,7 @@ an options argument: | ||||
| |`dateNF`     |  FMT 14  | Use specified date format in string output          | | ||||
| |`defval`     |          | Use specified value in place of null or undefined   | | ||||
| |`blankrows`  |    **    | Include blank lines in the output **                | | ||||
| |`skipHidden` |  false   | Do not generate objects for hidden rows/columns     | | ||||
| |`UTC`        |  false   | If true, dates will be correct in UTC **            | | ||||
| 
 | ||||
| - `raw` only affects cells which have a format code (`.z`) field or a formatted | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								docz/static/colprops/mac.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/colprops/mac.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/colprops/win.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/colprops/win.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/colprops/xlbox.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/colprops/xlbox.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 73 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/colprops/xlwidth.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/colprops/xlwidth.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/rowprops/mac.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/rowprops/mac.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 35 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/rowprops/win.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/rowprops/win.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 11 KiB | 
		Loading…
	
		Reference in New Issue
	
	Block a user