Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/xlsxwriter/vml.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

328 statements  

1############################################################################### 

2# 

3# Vml - A class for writing the Excel XLSX Vml file. 

4# 

5# SPDX-License-Identifier: BSD-2-Clause 

6# 

7# Copyright (c) 2013-2026, John McNamara, jmcnamara@cpan.org 

8# 

9 

10from typing import Optional 

11 

12from xlsxwriter import xmlwriter 

13 

14# Package imports. 

15from xlsxwriter.comments import CommentType 

16from xlsxwriter.image import Image 

17 

18 

19########################################################################### 

20# 

21# A button type class. 

22# 

23########################################################################### 

24class ButtonType: 

25 """ 

26 A class to represent a button in an Excel worksheet. 

27 

28 """ 

29 

30 def __init__( 

31 self, 

32 row: int, 

33 col: int, 

34 height: int, 

35 width: int, 

36 button_number: int, 

37 options: Optional[dict] = None, 

38 ) -> None: 

39 """ 

40 Initialize a ButtonType instance. 

41 

42 Args: 

43 row (int): The row number of the button. 

44 col (int): The column number of the button. 

45 height (int): The height of the button. 

46 width (int): The width of the button. 

47 button_number (int): The button number. 

48 options (dict): Additional options for the button. 

49 """ 

50 self.row = row 

51 self.col = col 

52 self.width = width 

53 self.height = height 

54 

55 self.macro = f"[0]!Button{button_number}_Click" 

56 self.caption = f"Button {button_number}" 

57 self.description = None 

58 

59 self.x_scale = 1 

60 self.y_scale = 1 

61 self.x_offset = 0 

62 self.y_offset = 0 

63 

64 self.vertices = [] 

65 

66 # Set any user supplied options. 

67 self._set_user_options(options) 

68 

69 def _set_user_options(self, options=None) -> None: 

70 """ 

71 This method handles the additional optional parameters to 

72 ``insert_button()``. 

73 """ 

74 if options is None: 

75 return 

76 

77 # Overwrite the defaults with any user supplied values. Incorrect or 

78 # misspelled parameters are silently ignored. 

79 self.width = options.get("width", self.width) 

80 self.height = options.get("height", self.height) 

81 self.caption = options.get("caption", self.caption) 

82 self.x_offset = options.get("x_offset", self.x_offset) 

83 self.y_offset = options.get("y_offset", self.y_offset) 

84 self.description = options.get("description", self.description) 

85 

86 # Set the macro name. 

87 if options.get("macro"): 

88 self.macro = "[0]!" + options["macro"] 

89 

90 # Scale the size of the button box if required. 

91 if options.get("x_scale"): 

92 self.width = self.width * options["x_scale"] 

93 

94 if options.get("y_scale"): 

95 self.height = self.height * options["y_scale"] 

96 

97 # Round the dimensions to the nearest pixel. 

98 self.width = int(0.5 + self.width) 

99 self.height = int(0.5 + self.height) 

100 

101 

102########################################################################### 

103# 

104# The file writer class for the Excel XLSX VML file. 

105# 

106########################################################################### 

107 

108 

109class Vml(xmlwriter.XMLwriter): 

110 """ 

111 A class for writing the Excel XLSX Vml file. 

112 

113 

114 """ 

115 

116 ########################################################################### 

117 # 

118 # Private API. 

119 # 

120 ########################################################################### 

121 def _assemble_xml_file( 

122 self, 

123 data_id, 

124 vml_shape_id, 

125 comments_data=None, 

126 buttons_data=None, 

127 header_images=None, 

128 ) -> None: 

129 # Assemble and write the XML file. 

130 z_index = 1 

131 

132 self._write_xml_namespace() 

133 

134 # Write the o:shapelayout element. 

135 self._write_shapelayout(data_id) 

136 

137 if buttons_data: 

138 # Write the v:shapetype element. 

139 self._write_button_shapetype() 

140 

141 for button in buttons_data: 

142 # Write the v:shape element. 

143 vml_shape_id += 1 

144 self._write_button_shape(vml_shape_id, z_index, button) 

145 z_index += 1 

146 

147 if comments_data: 

148 # Write the v:shapetype element. 

149 self._write_comment_shapetype() 

150 

151 for comment in comments_data: 

152 # Write the v:shape element. 

153 vml_shape_id += 1 

154 self._write_comment_shape(vml_shape_id, z_index, comment) 

155 z_index += 1 

156 

157 if header_images: 

158 # Write the v:shapetype element. 

159 self._write_image_shapetype() 

160 

161 index = 1 

162 for image in header_images: 

163 # Write the v:shape element. 

164 vml_shape_id += 1 

165 self._write_image_shape(vml_shape_id, index, image) 

166 index += 1 

167 

168 self._xml_end_tag("xml") 

169 

170 # Close the XML writer filehandle. 

171 self._xml_close() 

172 

173 def _pixels_to_points(self, vertices): 

174 # Convert comment vertices from pixels to points. 

175 

176 left, top, width, height = vertices[8:12] 

177 

178 # Scale to pixels. 

179 left *= 0.75 

180 top *= 0.75 

181 width *= 0.75 

182 height *= 0.75 

183 

184 return left, top, width, height 

185 

186 ########################################################################### 

187 # 

188 # XML methods. 

189 # 

190 ########################################################################### 

191 def _write_xml_namespace(self) -> None: 

192 # Write the <xml> element. This is the root element of VML. 

193 schema = "urn:schemas-microsoft-com:" 

194 xmlns = schema + "vml" 

195 xmlns_o = schema + "office:office" 

196 xmlns_x = schema + "office:excel" 

197 

198 attributes = [ 

199 ("xmlns:v", xmlns), 

200 ("xmlns:o", xmlns_o), 

201 ("xmlns:x", xmlns_x), 

202 ] 

203 

204 self._xml_start_tag("xml", attributes) 

205 

206 def _write_shapelayout(self, data_id) -> None: 

207 # Write the <o:shapelayout> element. 

208 attributes = [("v:ext", "edit")] 

209 

210 self._xml_start_tag("o:shapelayout", attributes) 

211 

212 # Write the o:idmap element. 

213 self._write_idmap(data_id) 

214 

215 self._xml_end_tag("o:shapelayout") 

216 

217 def _write_idmap(self, data_id) -> None: 

218 # Write the <o:idmap> element. 

219 attributes = [ 

220 ("v:ext", "edit"), 

221 ("data", data_id), 

222 ] 

223 

224 self._xml_empty_tag("o:idmap", attributes) 

225 

226 def _write_comment_shapetype(self) -> None: 

227 # Write the <v:shapetype> element. 

228 shape_id = "_x0000_t202" 

229 coordsize = "21600,21600" 

230 spt = 202 

231 path = "m,l,21600r21600,l21600,xe" 

232 

233 attributes = [ 

234 ("id", shape_id), 

235 ("coordsize", coordsize), 

236 ("o:spt", spt), 

237 ("path", path), 

238 ] 

239 

240 self._xml_start_tag("v:shapetype", attributes) 

241 

242 # Write the v:stroke element. 

243 self._write_stroke() 

244 

245 # Write the v:path element. 

246 self._write_comment_path("t", "rect") 

247 

248 self._xml_end_tag("v:shapetype") 

249 

250 def _write_button_shapetype(self) -> None: 

251 # Write the <v:shapetype> element. 

252 shape_id = "_x0000_t201" 

253 coordsize = "21600,21600" 

254 spt = 201 

255 path = "m,l,21600r21600,l21600,xe" 

256 

257 attributes = [ 

258 ("id", shape_id), 

259 ("coordsize", coordsize), 

260 ("o:spt", spt), 

261 ("path", path), 

262 ] 

263 

264 self._xml_start_tag("v:shapetype", attributes) 

265 

266 # Write the v:stroke element. 

267 self._write_stroke() 

268 

269 # Write the v:path element. 

270 self._write_button_path() 

271 

272 # Write the o:lock element. 

273 self._write_shapetype_lock() 

274 

275 self._xml_end_tag("v:shapetype") 

276 

277 def _write_image_shapetype(self) -> None: 

278 # Write the <v:shapetype> element. 

279 shape_id = "_x0000_t75" 

280 coordsize = "21600,21600" 

281 spt = 75 

282 o_preferrelative = "t" 

283 path = "m@4@5l@4@11@9@11@9@5xe" 

284 filled = "f" 

285 stroked = "f" 

286 

287 attributes = [ 

288 ("id", shape_id), 

289 ("coordsize", coordsize), 

290 ("o:spt", spt), 

291 ("o:preferrelative", o_preferrelative), 

292 ("path", path), 

293 ("filled", filled), 

294 ("stroked", stroked), 

295 ] 

296 

297 self._xml_start_tag("v:shapetype", attributes) 

298 

299 # Write the v:stroke element. 

300 self._write_stroke() 

301 

302 # Write the v:formulas element. 

303 self._write_formulas() 

304 

305 # Write the v:path element. 

306 self._write_image_path() 

307 

308 # Write the o:lock element. 

309 self._write_aspect_ratio_lock() 

310 

311 self._xml_end_tag("v:shapetype") 

312 

313 def _write_stroke(self) -> None: 

314 # Write the <v:stroke> element. 

315 joinstyle = "miter" 

316 

317 attributes = [("joinstyle", joinstyle)] 

318 

319 self._xml_empty_tag("v:stroke", attributes) 

320 

321 def _write_comment_path(self, gradientshapeok, connecttype) -> None: 

322 # Write the <v:path> element. 

323 attributes = [] 

324 

325 if gradientshapeok: 

326 attributes.append(("gradientshapeok", "t")) 

327 

328 attributes.append(("o:connecttype", connecttype)) 

329 

330 self._xml_empty_tag("v:path", attributes) 

331 

332 def _write_button_path(self) -> None: 

333 # Write the <v:path> element. 

334 shadowok = "f" 

335 extrusionok = "f" 

336 strokeok = "f" 

337 fillok = "f" 

338 connecttype = "rect" 

339 

340 attributes = [ 

341 ("shadowok", shadowok), 

342 ("o:extrusionok", extrusionok), 

343 ("strokeok", strokeok), 

344 ("fillok", fillok), 

345 ("o:connecttype", connecttype), 

346 ] 

347 

348 self._xml_empty_tag("v:path", attributes) 

349 

350 def _write_image_path(self) -> None: 

351 # Write the <v:path> element. 

352 extrusionok = "f" 

353 gradientshapeok = "t" 

354 connecttype = "rect" 

355 

356 attributes = [ 

357 ("o:extrusionok", extrusionok), 

358 ("gradientshapeok", gradientshapeok), 

359 ("o:connecttype", connecttype), 

360 ] 

361 

362 self._xml_empty_tag("v:path", attributes) 

363 

364 def _write_shapetype_lock(self) -> None: 

365 # Write the <o:lock> element. 

366 ext = "edit" 

367 shapetype = "t" 

368 

369 attributes = [ 

370 ("v:ext", ext), 

371 ("shapetype", shapetype), 

372 ] 

373 

374 self._xml_empty_tag("o:lock", attributes) 

375 

376 def _write_rotation_lock(self) -> None: 

377 # Write the <o:lock> element. 

378 ext = "edit" 

379 rotation = "t" 

380 

381 attributes = [ 

382 ("v:ext", ext), 

383 ("rotation", rotation), 

384 ] 

385 

386 self._xml_empty_tag("o:lock", attributes) 

387 

388 def _write_aspect_ratio_lock(self) -> None: 

389 # Write the <o:lock> element. 

390 ext = "edit" 

391 aspectratio = "t" 

392 

393 attributes = [ 

394 ("v:ext", ext), 

395 ("aspectratio", aspectratio), 

396 ] 

397 

398 self._xml_empty_tag("o:lock", attributes) 

399 

400 def _write_comment_shape(self, shape_id, z_index, comment: CommentType) -> None: 

401 # Write the <v:shape> element. 

402 shape_type = "#_x0000_t202" 

403 insetmode = "auto" 

404 visibility = "hidden" 

405 

406 # Set the shape index. 

407 shape_id = "_x0000_s" + str(shape_id) 

408 

409 left, top, width, height = self._pixels_to_points(comment.vertices) 

410 

411 # Set the visibility. 

412 if comment.is_visible: 

413 visibility = "visible" 

414 

415 style = ( 

416 f"position:absolute;" 

417 f"margin-left:{left:.15g}pt;" 

418 f"margin-top:{top:.15g}pt;" 

419 f"width:{width:.15g}pt;" 

420 f"height:{height:.15g}pt;" 

421 f"z-index:{z_index};" 

422 f"visibility:{visibility}" 

423 ) 

424 

425 attributes = [ 

426 ("id", shape_id), 

427 ("type", shape_type), 

428 ("style", style), 

429 ("fillcolor", comment.color._vml_rgb_hex_value()), 

430 ("o:insetmode", insetmode), 

431 ] 

432 

433 self._xml_start_tag("v:shape", attributes) 

434 

435 # Write the v:fill element. 

436 self._write_comment_fill() 

437 

438 # Write the v:shadow element. 

439 self._write_shadow() 

440 

441 # Write the v:path element. 

442 self._write_comment_path(None, "none") 

443 

444 # Write the v:textbox element. 

445 self._write_comment_textbox() 

446 

447 # Write the x:ClientData element. 

448 self._write_comment_client_data(comment) 

449 

450 self._xml_end_tag("v:shape") 

451 

452 def _write_button_shape(self, shape_id, z_index, button: ButtonType) -> None: 

453 # Write the <v:shape> element. 

454 shape_type = "#_x0000_t201" 

455 

456 # Set the shape index. 

457 shape_id = "_x0000_s" + str(shape_id) 

458 

459 left, top, width, height = self._pixels_to_points(button.vertices) 

460 

461 style = ( 

462 f"position:absolute;" 

463 f"margin-left:{left:.15g}pt;" 

464 f"margin-top:{top:.15g}pt;" 

465 f"width:{width:.15g}pt;" 

466 f"height:{height:.15g}pt;" 

467 f"z-index:{z_index};" 

468 f"mso-wrap-style:tight" 

469 ) 

470 

471 attributes = [ 

472 ("id", shape_id), 

473 ("type", shape_type), 

474 ] 

475 

476 if button.description is not None: 

477 attributes.append(("alt", button.description)) 

478 

479 attributes.append(("style", style)) 

480 attributes.append(("o:button", "t")) 

481 attributes.append(("fillcolor", "buttonFace [67]")) 

482 attributes.append(("strokecolor", "windowText [64]")) 

483 attributes.append(("o:insetmode", "auto")) 

484 

485 self._xml_start_tag("v:shape", attributes) 

486 

487 # Write the v:fill element. 

488 self._write_button_fill() 

489 

490 # Write the o:lock element. 

491 self._write_rotation_lock() 

492 

493 # Write the v:textbox element. 

494 self._write_button_textbox(button) 

495 

496 # Write the x:ClientData element. 

497 self._write_button_client_data(button) 

498 

499 self._xml_end_tag("v:shape") 

500 

501 def _write_image_shape(self, shape_id, z_index, image: Image) -> None: 

502 # Write the <v:shape> element. 

503 shape_type = "#_x0000_t75" 

504 

505 # Set the shape index. 

506 shape_id = "_x0000_s" + str(shape_id) 

507 

508 # Get the image parameters 

509 name = image.image_name 

510 width = image._width 

511 x_dpi = image._x_dpi 

512 y_dpi = image._y_dpi 

513 height = image._height 

514 ref_id = image._ref_id 

515 position = image._header_position 

516 

517 # Scale the height/width by the resolution, relative to 72dpi. 

518 width = width * 72.0 / x_dpi 

519 height = height * 72.0 / y_dpi 

520 

521 # Excel uses a rounding based around 72 and 96 dpi. 

522 width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25) 

523 height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25) 

524 

525 style = ( 

526 f"position:absolute;" 

527 f"margin-left:0;" 

528 f"margin-top:0;" 

529 f"width:{width:.15g}pt;" 

530 f"height:{height:.15g}pt;" 

531 f"z-index:{z_index}" 

532 ) 

533 

534 attributes = [ 

535 ("id", position), 

536 ("o:spid", shape_id), 

537 ("type", shape_type), 

538 ("style", style), 

539 ] 

540 

541 self._xml_start_tag("v:shape", attributes) 

542 

543 # Write the v:imagedata element. 

544 self._write_imagedata(ref_id, name) 

545 

546 # Write the o:lock element. 

547 self._write_rotation_lock() 

548 

549 self._xml_end_tag("v:shape") 

550 

551 def _write_comment_fill(self) -> None: 

552 # Write the <v:fill> element. 

553 color_2 = "#ffffe1" 

554 

555 attributes = [("color2", color_2)] 

556 

557 self._xml_empty_tag("v:fill", attributes) 

558 

559 def _write_button_fill(self) -> None: 

560 # Write the <v:fill> element. 

561 color_2 = "buttonFace [67]" 

562 detectmouseclick = "t" 

563 

564 attributes = [ 

565 ("color2", color_2), 

566 ("o:detectmouseclick", detectmouseclick), 

567 ] 

568 

569 self._xml_empty_tag("v:fill", attributes) 

570 

571 def _write_shadow(self) -> None: 

572 # Write the <v:shadow> element. 

573 on = "t" 

574 color = "black" 

575 obscured = "t" 

576 

577 attributes = [ 

578 ("on", on), 

579 ("color", color), 

580 ("obscured", obscured), 

581 ] 

582 

583 self._xml_empty_tag("v:shadow", attributes) 

584 

585 def _write_comment_textbox(self) -> None: 

586 # Write the <v:textbox> element. 

587 style = "mso-direction-alt:auto" 

588 

589 attributes = [("style", style)] 

590 

591 self._xml_start_tag("v:textbox", attributes) 

592 

593 # Write the div element. 

594 self._write_div("left") 

595 

596 self._xml_end_tag("v:textbox") 

597 

598 def _write_button_textbox(self, button: ButtonType) -> None: 

599 # Write the <v:textbox> element. 

600 style = "mso-direction-alt:auto" 

601 

602 attributes = [("style", style), ("o:singleclick", "f")] 

603 

604 self._xml_start_tag("v:textbox", attributes) 

605 

606 # Write the div element. 

607 self._write_div("center", button.caption) 

608 

609 self._xml_end_tag("v:textbox") 

610 

611 def _write_div(self, align: str, caption: Optional[str] = None) -> None: 

612 # Write the <div> element. 

613 

614 style = "text-align:" + align 

615 

616 attributes = [("style", style)] 

617 

618 self._xml_start_tag("div", attributes) 

619 

620 if caption: 

621 self._write_button_font(caption) 

622 

623 self._xml_end_tag("div") 

624 

625 def _write_button_font(self, caption: str) -> None: 

626 # Write the <font> element. 

627 face = "Calibri" 

628 size = 220 

629 color = "#000000" 

630 

631 attributes = [ 

632 ("face", face), 

633 ("size", size), 

634 ("color", color), 

635 ] 

636 

637 self._xml_data_element("font", caption, attributes) 

638 

639 def _write_comment_client_data(self, comment: CommentType) -> None: 

640 # Write the <x:ClientData> element. 

641 object_type = "Note" 

642 

643 attributes = [("ObjectType", object_type)] 

644 

645 self._xml_start_tag("x:ClientData", attributes) 

646 

647 # Write the x:MoveWithCells element. 

648 self._write_move_with_cells() 

649 

650 # Write the x:SizeWithCells element. 

651 self._write_size_with_cells() 

652 

653 # Write the x:Anchor element. 

654 self._write_anchor(comment.vertices) 

655 

656 # Write the x:AutoFill element. 

657 self._write_auto_fill() 

658 

659 # Write the x:Row element. 

660 self._write_row(comment.row) 

661 

662 # Write the x:Column element. 

663 self._write_column(comment.col) 

664 

665 # Write the x:Visible element. 

666 if comment.is_visible: 

667 self._write_visible() 

668 

669 self._xml_end_tag("x:ClientData") 

670 

671 def _write_button_client_data(self, button) -> None: 

672 # Write the <x:ClientData> element. 

673 object_type = "Button" 

674 

675 attributes = [("ObjectType", object_type)] 

676 

677 self._xml_start_tag("x:ClientData", attributes) 

678 

679 # Write the x:Anchor element. 

680 self._write_anchor(button.vertices) 

681 

682 # Write the x:PrintObject element. 

683 self._write_print_object() 

684 

685 # Write the x:AutoFill element. 

686 self._write_auto_fill() 

687 

688 # Write the x:FmlaMacro element. 

689 self._write_fmla_macro(button.macro) 

690 

691 # Write the x:TextHAlign element. 

692 self._write_text_halign() 

693 

694 # Write the x:TextVAlign element. 

695 self._write_text_valign() 

696 

697 self._xml_end_tag("x:ClientData") 

698 

699 def _write_move_with_cells(self) -> None: 

700 # Write the <x:MoveWithCells> element. 

701 self._xml_empty_tag("x:MoveWithCells") 

702 

703 def _write_size_with_cells(self) -> None: 

704 # Write the <x:SizeWithCells> element. 

705 self._xml_empty_tag("x:SizeWithCells") 

706 

707 def _write_visible(self) -> None: 

708 # Write the <x:Visible> element. 

709 self._xml_empty_tag("x:Visible") 

710 

711 def _write_anchor(self, vertices) -> None: 

712 # Write the <x:Anchor> element. 

713 col_start, row_start, x1, y1, col_end, row_end, x2, y2 = vertices[:8] 

714 

715 strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2] 

716 strings = [str(i) for i in strings] 

717 

718 data = ", ".join(strings) 

719 

720 self._xml_data_element("x:Anchor", data) 

721 

722 def _write_auto_fill(self) -> None: 

723 # Write the <x:AutoFill> element. 

724 data = "False" 

725 

726 self._xml_data_element("x:AutoFill", data) 

727 

728 def _write_row(self, data) -> None: 

729 # Write the <x:Row> element. 

730 self._xml_data_element("x:Row", data) 

731 

732 def _write_column(self, data) -> None: 

733 # Write the <x:Column> element. 

734 self._xml_data_element("x:Column", data) 

735 

736 def _write_print_object(self) -> None: 

737 # Write the <x:PrintObject> element. 

738 self._xml_data_element("x:PrintObject", "False") 

739 

740 def _write_text_halign(self) -> None: 

741 # Write the <x:TextHAlign> element. 

742 self._xml_data_element("x:TextHAlign", "Center") 

743 

744 def _write_text_valign(self) -> None: 

745 # Write the <x:TextVAlign> element. 

746 self._xml_data_element("x:TextVAlign", "Center") 

747 

748 def _write_fmla_macro(self, data) -> None: 

749 # Write the <x:FmlaMacro> element. 

750 self._xml_data_element("x:FmlaMacro", data) 

751 

752 def _write_imagedata(self, ref_id, o_title) -> None: 

753 # Write the <v:imagedata> element. 

754 attributes = [ 

755 ("o:relid", "rId" + str(ref_id)), 

756 ("o:title", o_title), 

757 ] 

758 

759 self._xml_empty_tag("v:imagedata", attributes) 

760 

761 def _write_formulas(self) -> None: 

762 # Write the <v:formulas> element. 

763 self._xml_start_tag("v:formulas") 

764 

765 # Write the v:f elements. 

766 self._write_formula("if lineDrawn pixelLineWidth 0") 

767 self._write_formula("sum @0 1 0") 

768 self._write_formula("sum 0 0 @1") 

769 self._write_formula("prod @2 1 2") 

770 self._write_formula("prod @3 21600 pixelWidth") 

771 self._write_formula("prod @3 21600 pixelHeight") 

772 self._write_formula("sum @0 0 1") 

773 self._write_formula("prod @6 1 2") 

774 self._write_formula("prod @7 21600 pixelWidth") 

775 self._write_formula("sum @8 21600 0") 

776 self._write_formula("prod @7 21600 pixelHeight") 

777 self._write_formula("sum @10 21600 0") 

778 

779 self._xml_end_tag("v:formulas") 

780 

781 def _write_formula(self, eqn) -> None: 

782 # Write the <v:f> element. 

783 attributes = [("eqn", eqn)] 

784 

785 self._xml_empty_tag("v:f", attributes)