Losing merged cells border while editing Excel file with openpyxl

后端 未结 5 1398
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-09 13:22

I have two sheets in an Excel file and the first one is a cover sheet which I don\'t need to edit. There are a few merged cells in the cover sheet, and when I edit the file usin

相关标签:
5条回答
  • 2021-02-09 14:05

    Actual solution is to patch the libraries code by including this snippet after including the library, it fixes the problem. (Note: don't worry about missing definitions, e.g. COORD_RE, i.e. the patch is self-contained)

    from itertools import product
    import types
    import openpyxl
    from openpyxl import worksheet
    from openpyxl.utils import range_boundaries
    
    
    def patch_worksheet():
        """This monkeypatches Worksheet.merge_cells to remove cell deletion bug
        https://bitbucket.org/openpyxl/openpyxl/issues/365/styling-merged-cells-isnt-working
        Thank you to Sergey Pikhovkin for the fix
        """
    
        def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
            """ Set merge on a cell range.  Range is a cell range (e.g. A1:E1)
            This is monkeypatched to remove cell deletion bug
            https://bitbucket.org/openpyxl/openpyxl/issues/365/styling-merged-cells-isnt-working
            """
            if not range_string and not all((start_row, start_column, end_row, end_column)):
                msg = "You have to provide a value either for 'coordinate' or for\
                'start_row', 'start_column', 'end_row' *and* 'end_column'"
                raise ValueError(msg)
            elif not range_string:
                range_string = '%s%s:%s%s' % (get_column_letter(start_column),
                                              start_row,
                                              get_column_letter(end_column),
                                              end_row)
            elif ":" not in range_string:
                if COORD_RE.match(range_string):
                    return  # Single cell, do nothing
                raise ValueError("Range must be a cell range (e.g. A1:E1)")
            else:
                range_string = range_string.replace('$', '')
    
            if range_string not in self._merged_cells:
                self._merged_cells.append(range_string)
    
    
            # The following is removed by this monkeypatch:
    
            # min_col, min_row, max_col, max_row = range_boundaries(range_string)
            # rows = range(min_row, max_row+1)
            # cols = range(min_col, max_col+1)
            # cells = product(rows, cols)
    
            # all but the top-left cell are removed
            #for c in islice(cells, 1, None):
                #if c in self._cells:
                    #del self._cells[c]
    
        # Apply monkey patch
        worksheet.Worksheet.merge_cells = merge_cells
    patch_worksheet()
    

    Source https://bitbucket.org/openpyxl/openpyxl/issues/365/styling-merged-cells-isnt-working

    0 讨论(0)
  • 2021-02-09 14:08

    For me this code work perfectly:

    wb = load_workbook(template_path)
    ws = wb.active
    for merged_cells in ws.merged_cells.ranges:
    style = ws.cell(merged_cells.min_row, merged_cells.min_col)._style
    for col in range(merged_cells.min_col, merged_cells.max_col + 1):
        for row in range(merged_cells.min_row, merged_cells.max_row + 1): 
            ws.cell(row, col)._style = style
    

    I simply find all merged cells and apply style of left top cell to the rest of the cells.

    0 讨论(0)
  • 2021-02-09 14:17

    If you are using openpyxl 2.4, the solution offered by micki_mouse may not work. I managed to make it work by tweaking it this way:

    from openpyxl import load_workbook
    from openpyxl.utils import range_boundaries
    
    wb = load_workbook(template_path)
    ws = wb.active
    for merged_cells in ws.merged_cell_ranges:
        min_col, min_row, max_col, max_row = range_boundaries(merged_cells)
        style = ws.cell(row=min_row, column=min_col)._style
        for col in range(min_col, max_col + 1):
            for row in range(min_row, max_row + 1):
            ws.cell(row=row, column=col)._style = style
    

    Basically, you have to use "merged_cell_ranges" (deprecated in 2.5.0-b1) to correctly access the merged cells within the worksheet. You also must specify explicitely in the cell constructor the row and column

    0 讨论(0)
  • 2021-02-09 14:21

    Simple method

    1. Go to the folder C:\Python\Lib\site-packages\ openpyxl\worksheet folder\

    2. Open worksheet.py file

    3. In merge_cells function comment the following lines

      min_col, min_row, max_col, max_row = 
         range_boundaries(range_string)
      rows = range(min_row, max_row+1)
      cols = range(min_col, max_col+1)
      cells = product(rows, cols)
      
      all but the top-left cell are removed
      for c in islice(cells, 1, None):
          if c in self._cells:
              del self._cells[c]
      
    4. Save it.

    That's all , it works for me.

    0 讨论(0)
  • 2021-02-09 14:26

    I know this is old but I got the same issue and the patch didn't work for me so I found a work around by using the function in the documentation that adds a style for merged cells and then looping on all the merged cells and calling the function on each range

    from openpyxl import load_workbook
    from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, 
    Alignment
    
    def style_range(ws, cell_range, border=Border(), fill=None, font=None, 
    alignment=None):
        """
        Apply styles to a range of cells as if they were a single cell.
    
        :param ws:  Excel worksheet instance
        :param range: An excel range to style (e.g. A1:F20)
        :param border: An openpyxl Border
        :param fill: An openpyxl PatternFill or GradientFill
        :param font: An openpyxl Font object
        """
    
        top = Border(top=border.top)
        left = Border(left=border.left)
        right = Border(right=border.right)
        bottom = Border(bottom=border.bottom)
    
        first_cell = ws[cell_range.split(":")[0]]
        if alignment:
            ws.merge_cells(cell_range)
            first_cell.alignment = alignment
    
        rows = ws[cell_range]
        if font:
            first_cell.font = font
    
        for cell in rows[0]:
            cell.border = cell.border + top
        for cell in rows[-1]:
            cell.border = cell.border + bottom
    
        for row in rows:
            l = row[0]
            r = row[-1]
            l.border = l.border + left
            r.border = r.border + right
            if fill:
                for c in row:
                    c.fill = fill
    
    file = 'file.xlsx'
    
    wb = load_workbook(file)
    ws = wb['Table 1']
    
    thin = Side(border_style="thin", color="000000")
    border = Border(top=thin, left=thin, right=thin, bottom=thin)
    
    for range in ws.merged_cells.ranges:
        style_range(ws, str(range), border=border)
    
    wb.save('newExcel.xlsx')
    
    0 讨论(0)
提交回复
热议问题