作者 唐旭辉

添加依赖

要显示太多修改。

为保证性能只显示 18 of 18+ 个文件。

  1 +{
  2 + // 使用 IntelliSense 了解相关属性。
  3 + // 悬停以查看现有属性的描述。
  4 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  5 + "version": "0.2.0",
  6 + "configurations": [
  7 + {
  8 + "name": "Launch",
  9 + "type": "go",
  10 + "request": "launch",
  11 + "mode": "auto",
  12 + "program": "${workspaceFolder}/main.go",
  13 + "env": {},
  14 + "args": []
  15 + }
  16 + ]
  17 +}
  1 +~$*.xlsx
  2 +test/Test*.xlsx
  3 +*.out
  4 +*.test
  1 +language: go
  2 +
  3 +install:
  4 + - go get -d -t -v ./... && go build -v ./...
  5 +
  6 +go:
  7 + - 1.10.x
  8 + - 1.11.x
  9 + - 1.12.x
  10 +
  11 +os:
  12 + - linux
  13 + - osx
  14 +
  15 +env:
  16 + matrix:
  17 + - GOARCH=amd64
  18 + - GOARCH=386
  19 +
  20 +script:
  21 + - go vet ./...
  22 + - go test ./... -v -coverprofile=coverage.txt -covermode=atomic
  23 +
  24 +after_success:
  25 + - bash <(curl -s https://codecov.io/bash)
  1 +# Contributor Covenant Code of Conduct
  2 +
  3 +## Our Pledge
  4 +
  5 +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
  6 +
  7 +## Our Standards
  8 +
  9 +Examples of behavior that contributes to creating a positive environment include:
  10 +
  11 +* Using welcoming and inclusive language
  12 +* Being respectful of differing viewpoints and experiences
  13 +* Gracefully accepting constructive criticism
  14 +* Focusing on what is best for the community
  15 +* Showing empathy towards other community members
  16 +
  17 +Examples of unacceptable behavior by participants include:
  18 +
  19 +* The use of sexualized language or imagery and unwelcome sexual attention or advances
  20 +* Trolling, insulting/derogatory comments, and personal or political attacks
  21 +* Public or private harassment
  22 +* Publishing others' private information, such as a physical or electronic address, without explicit permission
  23 +* Other conduct which could reasonably be considered inappropriate in a professional setting
  24 +
  25 +## Our Responsibilities
  26 +
  27 +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
  28 +
  29 +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
  30 +
  31 +## Scope
  32 +
  33 +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
  34 +
  35 +## Enforcement
  36 +
  37 +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
  38 +
  39 +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
  40 +
  41 +## Attribution
  42 +
  43 +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
  44 +
  45 +[homepage]: http://contributor-covenant.org
  46 +[version]: http://contributor-covenant.org/version/1/4/
  1 +# Contributing to excelize
  2 +
  3 +Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and
  4 +guidelines useful to experienced open source contributors. Finally, make sure
  5 +you read our [community guidelines](#community-guidelines) before you
  6 +start participating.
  7 +
  8 +## Topics
  9 +
  10 +* [Reporting Security Issues](#reporting-security-issues)
  11 +* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
  12 +* [Reporting Issues](#reporting-other-issues)
  13 +* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
  14 +* [Community Guidelines](#community-guidelines)
  15 +
  16 +## Reporting security issues
  17 +
  18 +The excelize maintainers take security seriously. If you discover a security
  19 +issue, please bring it to their attention right away!
  20 +
  21 +Please **DO NOT** file a public issue, instead send your report privately to
  22 +[xuri.me](https://xuri.me).
  23 +
  24 +Security reports are greatly appreciated and we will publicly thank you for it.
  25 +We currently do not offer a paid security bounty program, but are not
  26 +ruling it out in the future.
  27 +
  28 +## Reporting other issues
  29 +
  30 +A great way to contribute to the project is to send a detailed report when you
  31 +encounter an issue. We always appreciate a well-written, thorough bug report,
  32 +and will thank you for it!
  33 +
  34 +Check that [our issue database](https://github.com/360EntSecGroup-Skylar/excelize/issues)
  35 +doesn't already include that problem or suggestion before submitting an issue.
  36 +If you find a match, you can use the "subscribe" button to get notified on
  37 +updates. Do *not* leave random "+1" or "I have this too" comments, as they
  38 +only clutter the discussion, and don't help resolving it. However, if you
  39 +have ways to reproduce the issue or have additional information that may help
  40 +resolving the issue, please leave a comment.
  41 +
  42 +When reporting issues, always include the output of `go env`.
  43 +
  44 +Also include the steps required to reproduce the problem if possible and
  45 +applicable. This information will help us review and fix your issue faster.
  46 +When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com).
  47 +Don't forget to remove sensitive data from your logfiles before posting (you can
  48 +replace those parts with "REDACTED").
  49 +
  50 +## Quick contribution tips and guidelines
  51 +
  52 +This section gives the experienced contributor some tips and guidelines.
  53 +
  54 +### Pull requests are always welcome
  55 +
  56 +Not sure if that typo is worth a pull request? Found a bug and know how to fix
  57 +it? Do it! We will appreciate it. Any significant improvement should be
  58 +documented as [a GitHub issue](https://github.com/360EntSecGroup-Skylar/excelize/issues) before
  59 +anybody starts working on it.
  60 +
  61 +We are always thrilled to receive pull requests. We do our best to process them
  62 +quickly. If your pull request is not accepted on the first try,
  63 +don't get discouraged!
  64 +
  65 +### Design and cleanup proposals
  66 +
  67 +You can propose new designs for existing excelize features. You can also design
  68 +entirely new features. We really appreciate contributors who want to refactor or
  69 +otherwise cleanup our project.
  70 +
  71 +We try hard to keep excelize lean and focused. Excelize can't do everything for
  72 +everybody. This means that we might decide against incorporating a new feature.
  73 +However, there might be a way to implement that feature *on top of* excelize.
  74 +
  75 +### Conventions
  76 +
  77 +Fork the repository and make changes on your fork in a feature branch:
  78 +
  79 +* If it's a bug fix branch, name it XXXX-something where XXXX is the number of
  80 + the issue.
  81 +* If it's a feature branch, create an enhancement issue to announce
  82 + your intentions, and name it XXXX-something where XXXX is the number of the
  83 + issue.
  84 +
  85 +Submit unit tests for your changes. Go has a great test framework built in; use
  86 +it! Take a look at existing tests for inspiration. Run the full test on your branch before
  87 +submitting a pull request.
  88 +
  89 +Update the documentation when creating or modifying features. Test your
  90 +documentation changes for clarity, concision, and correctness, as well as a
  91 +clean documentation build.
  92 +
  93 +Write clean code. Universally formatted code promotes ease of writing, reading,
  94 +and maintenance. Always run `gofmt -s -w file.go` on each changed file before
  95 +committing your changes. Most editors have plug-ins that do this automatically.
  96 +
  97 +Pull request descriptions should be as clear as possible and include a reference
  98 +to all the issues that they address.
  99 +
  100 +### Successful Changes
  101 +
  102 +Before contributing large or high impact changes, make the effort to coordinate
  103 +with the maintainers of the project before submitting a pull request. This
  104 +prevents you from doing extra work that may or may not be merged.
  105 +
  106 +Large PRs that are just submitted without any prior communication are unlikely
  107 +to be successful.
  108 +
  109 +While pull requests are the methodology for submitting changes to code, changes
  110 +are much more likely to be accepted if they are accompanied by additional
  111 +engineering work. While we don't define this explicitly, most of these goals
  112 +are accomplished through communication of the design goals and subsequent
  113 +solutions. Often times, it helps to first state the problem before presenting
  114 +solutions.
  115 +
  116 +Typically, the best methods of accomplishing this are to submit an issue,
  117 +stating the problem. This issue can include a problem statement and a
  118 +checklist with requirements. If solutions are proposed, alternatives should be
  119 +listed and eliminated. Even if the criteria for elimination of a solution is
  120 +frivolous, say so.
  121 +
  122 +Larger changes typically work best with design documents. These are focused on
  123 +providing context to the design at the time the feature was conceived and can
  124 +inform future documentation contributions.
  125 +
  126 +### Commit Messages
  127 +
  128 +Commit messages must start with a capitalized and short summary
  129 +written in the imperative, followed by an optional, more detailed explanatory
  130 +text which is separated from the summary by an empty line.
  131 +
  132 +Commit messages should follow best practices, including explaining the context
  133 +of the problem and how it was solved, including in caveats or follow up changes
  134 +required. They should tell the story of the change and provide readers
  135 +understanding of what led to it.
  136 +
  137 +In practice, the best approach to maintaining a nice commit message is to
  138 +leverage a `git add -p` and `git commit --amend` to formulate a solid
  139 +changeset. This allows one to piece together a change, as information becomes
  140 +available.
  141 +
  142 +If you squash a series of commits, don't just submit that. Re-write the commit
  143 +message, as if the series of commits was a single stroke of brilliance.
  144 +
  145 +That said, there is no requirement to have a single commit for a PR, as long as
  146 +each commit tells the story. For example, if there is a feature that requires a
  147 +package, it might make sense to have the package in a separate commit then have
  148 +a subsequent commit that uses it.
  149 +
  150 +Remember, you're telling part of the story with the commit message. Don't make
  151 +your chapter weird.
  152 +
  153 +### Review
  154 +
  155 +Code review comments may be added to your pull request. Discuss, then make the
  156 +suggested modifications and push additional commits to your feature branch. Post
  157 +a comment after pushing. New commits show up in the pull request automatically,
  158 +but the reviewers are notified only when you comment.
  159 +
  160 +Pull requests must be cleanly rebased on top of master without multiple branches
  161 +mixed into the PR.
  162 +
  163 +**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
  164 +feature branch to update your pull request rather than `merge master`.
  165 +
  166 +Before you make a pull request, squash your commits into logical units of work
  167 +using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
  168 +set of patches that should be reviewed together: for example, upgrading the
  169 +version of a vendored dependency and taking advantage of its now available new
  170 +feature constitute two separate units of work. Implementing a new function and
  171 +calling it in another file constitute a single logical unit of work. The very
  172 +high majority of submissions should have a single commit, so if in doubt: squash
  173 +down to one.
  174 +
  175 +After every commit, make sure the test passes. Include documentation
  176 +changes in the same pull request so that a revert would remove all traces of
  177 +the feature or fix.
  178 +
  179 +Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
  180 +close an issue. Including references automatically closes the issue on a merge.
  181 +
  182 +Please see the [Coding Style](#coding-style) for further guidelines.
  183 +
  184 +### Merge approval
  185 +
  186 +The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to
  187 +indicate acceptance.
  188 +
  189 +### Sign your work
  190 +
  191 +The sign-off is a simple line at the end of the explanation for the patch. Your
  192 +signature certifies that you wrote the patch or otherwise have the right to pass
  193 +it on as an open-source patch. The rules are pretty simple: if you can certify
  194 +the below (from [developercertificate.org](http://developercertificate.org/)):
  195 +
  196 +```text
  197 +Developer Certificate of Origin
  198 +Version 1.1
  199 +
  200 +Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
  201 +1 Letterman Drive
  202 +Suite D4700
  203 +San Francisco, CA, 94129
  204 +
  205 +Everyone is permitted to copy and distribute verbatim copies of this
  206 +license document, but changing it is not allowed.
  207 +
  208 +Developer's Certificate of Origin 1.1
  209 +
  210 +By making a contribution to this project, I certify that:
  211 +
  212 +(a) The contribution was created in whole or in part by me and I
  213 + have the right to submit it under the open source license
  214 + indicated in the file; or
  215 +
  216 +(b) The contribution is based upon previous work that, to the best
  217 + of my knowledge, is covered under an appropriate open source
  218 + license and I have the right under that license to submit that
  219 + work with modifications, whether created in whole or in part
  220 + by me, under the same open source license (unless I am
  221 + permitted to submit under a different license), as indicated
  222 + in the file; or
  223 +
  224 +(c) The contribution was provided directly to me by some other
  225 + person who certified (a), (b) or (c) and I have not modified
  226 + it.
  227 +
  228 +(d) I understand and agree that this project and the contribution
  229 + are public and that a record of the contribution (including all
  230 + personal information I submit with it, including my sign-off) is
  231 + maintained indefinitely and may be redistributed consistent with
  232 + this project or the open source license(s) involved.
  233 +```
  234 +
  235 +Then you just add a line to every git commit message:
  236 +
  237 + Signed-off-by: Ri Xu https://xuri.me
  238 +
  239 +Use your real name (sorry, no pseudonyms or anonymous contributions.)
  240 +
  241 +If you set your `user.name` and `user.email` git configs, you can sign your
  242 +commit automatically with `git commit -s`.
  243 +
  244 +### How can I become a maintainer
  245 +
  246 +First, all maintainers have 3 things
  247 +
  248 +* They share responsibility in the project's success.
  249 +* They have made a long-term, recurring time investment to improve the project.
  250 +* They spend that time doing whatever needs to be done, not necessarily what
  251 + is the most interesting or fun.
  252 +
  253 +Maintainers are often under-appreciated, because their work is harder to appreciate.
  254 +It's easy to appreciate a really cool and technically advanced feature. It's harder
  255 +to appreciate the absence of bugs, the slow but steady improvement in stability,
  256 +or the reliability of a release process. But those things distinguish a good
  257 +project from a great one.
  258 +
  259 +Don't forget: being a maintainer is a time investment. Make sure you
  260 +will have time to make yourself available. You don't have to be a
  261 +maintainer to make a difference on the project!
  262 +
  263 +If you want to become a meintainer, contact [xuri.me](https://xuri.me) and given a introduction of you.
  264 +
  265 +## Community guidelines
  266 +
  267 +We want to keep the community awesome, growing and collaborative. We need
  268 +your help to keep it that way. To help with this we've come up with some general
  269 +guidelines for the community as a whole:
  270 +
  271 +* Be nice: Be courteous, respectful and polite to fellow community members:
  272 + no regional, racial, gender, or other abuse will be tolerated. We like
  273 + nice people way better than mean ones!
  274 +
  275 +* Encourage diversity and participation: Make everyone in our community feel
  276 + welcome, regardless of their background and the extent of their
  277 + contributions, and do everything possible to encourage participation in
  278 + our community.
  279 +
  280 +* Keep it legal: Basically, don't get us in trouble. Share only content that
  281 + you own, do not share private or sensitive information, and don't break
  282 + the law.
  283 +
  284 +* Stay on topic: Make sure that you are posting to the correct channel and
  285 + avoid off-topic discussions. Remember when you update an issue or respond
  286 + to an email you are potentially sending to a large number of people. Please
  287 + consider this before you update. Also remember that nobody likes spam.
  288 +
  289 +* Don't send email to the maintainers: There's no need to send email to the
  290 + maintainers to ask them to investigate an issue or to take a look at a
  291 + pull request. Instead of sending an email, GitHub mentions should be
  292 + used to ping maintainers to review a pull request, a proposal or an
  293 + issue.
  294 +
  295 +### Guideline violations — 3 strikes method
  296 +
  297 +The point of this section is not to find opportunities to punish people, but we
  298 +do need a fair way to deal with people who are making our community suck.
  299 +
  300 +1. First occurrence: We'll give you a friendly, but public reminder that the
  301 + behavior is inappropriate according to our guidelines.
  302 +
  303 +2. Second occurrence: We will send you a private message with a warning that
  304 + any additional violations will result in removal from the community.
  305 +
  306 +3. Third occurrence: Depending on the violation, we may need to delete or ban
  307 + your account.
  308 +
  309 +**Notes:**
  310 +
  311 +* Obvious spammers are banned on first occurrence. If we don't do this, we'll
  312 + have spam all over the place.
  313 +
  314 +* Violations are forgiven after 6 months of good behavior, and we won't hold a
  315 + grudge.
  316 +
  317 +* People who commit minor infractions will get some education, rather than
  318 + hammering them in the 3 strikes process.
  319 +
  320 +* The rules apply equally to everyone in the community, no matter how much
  321 + you've contributed.
  322 +
  323 +* Extreme violations of a threatening, abusive, destructive or illegal nature
  324 + will be addressed immediately and are not subject to 3 strikes or forgiveness.
  325 +
  326 +* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of
  327 + appeals, we know that mistakes happen, and we'll work with you to come up with a
  328 + fair solution if there has been a misunderstanding.
  329 +
  330 +## Coding Style
  331 +
  332 +Unless explicitly stated, we follow all coding guidelines from the Go
  333 +community. While some of these standards may seem arbitrary, they somehow seem
  334 +to result in a solid, consistent codebase.
  335 +
  336 +It is possible that the code base does not currently comply with these
  337 +guidelines. We are not looking for a massive PR that fixes this, since that
  338 +goes against the spirit of the guidelines. All new contributions should make a
  339 +best effort to clean up and make the code base better than they left it.
  340 +Obviously, apply your best judgement. Remember, the goal here is to make the
  341 +code base easier for humans to navigate and understand. Always keep that in
  342 +mind when nudging others to comply.
  343 +
  344 +The rules:
  345 +
  346 +1. All code should be formatted with `gofmt -s`.
  347 +2. All code should pass the default levels of
  348 + [`golint`](https://github.com/golang/lint).
  349 +3. All code should follow the guidelines covered in [Effective
  350 + Go](http://golang.org/doc/effective_go.html) and [Go Code Review
  351 + Comments](https://github.com/golang/go/wiki/CodeReviewComments).
  352 +4. Comment the code. Tell us the why, the history and the context.
  353 +5. Document _all_ declarations and methods, even private ones. Declare
  354 + expectations, caveats and anything else that may be important. If a type
  355 + gets exported, having the comments already there will ensure it's ready.
  356 +6. Variable name length should be proportional to its context and no longer.
  357 + `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
  358 + In practice, short methods will have short variable names and globals will
  359 + have longer names.
  360 +7. No underscores in package names. If you need a compound name, step back,
  361 + and re-examine why you need a compound name. If you still think you need a
  362 + compound name, lose the underscore.
  363 +8. No utils or helpers packages. If a function is not general enough to
  364 + warrant its own package, it has not been written generally enough to be a
  365 + part of a util package. Just leave it unexported and well-documented.
  366 +9. All tests should run with `go test` and outside tooling should not be
  367 + required. No, we don't need another unit testing framework. Assertion
  368 + packages are acceptable if they provide _real_ incremental value.
  369 +10. Even though we call these "rules" above, they are actually just
  370 + guidelines. Since you've read all the rules, you now know that.
  371 +
  372 +If you are having trouble getting into the mood of idiomatic Go, we recommend
  373 +reading through [Effective Go](https://golang.org/doc/effective_go.html). The
  374 +[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
  375 +kool-aid is a lot easier than going thirsty.
  376 +
  377 +## Code Review Comments and Effective Go Guidelines
  378 +
  379 +[CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
  380 +
  381 +### Package Comment
  382 +
  383 +Every package should have a package comment, a block comment preceding the package clause.
  384 +For multi-file packages, the package comment only needs to be present in one file, and any one will do.
  385 +The package comment should introduce the package and provide information relevant to the package as a
  386 +whole. It will appear first on the godoc page and should set up the detailed documentation that follows.
  387 +
  388 +### Single Method Interface Name
  389 +
  390 +By convention, one-method interfaces are named by the method name plus an -er suffix
  391 +or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
  392 +
  393 +There are a number of such names and it's productive to honor them and the function names they capture.
  394 +Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion,
  395 +don't give your method one of those names unless it has the same signature and meaning. Conversely,
  396 +if your type implements a method with the same meaning as a method on a well-known type, give it the
  397 +same name and signature; call your string-converter method String not ToString.
  398 +
  399 +### Avoid Annotations in Comments
  400 +
  401 +Comments do not need extra formatting such as banners of stars. The generated output
  402 +may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc,
  403 +like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other
  404 +annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc
  405 +does do is to display indented text in a fixed-width font, suitable for program snippets.
  406 +The package comment for the fmt package uses this to good effect.
  407 +
  408 +### Comment First Word as Subject
  409 +
  410 +Doc comments work best as complete sentences, which allow a wide variety of automated presentations.
  411 +The first sentence should be a one-sentence summary that starts with the name being declared.
  412 +
  413 +### Good Package Name
  414 +
  415 +It's helpful if everyone using the package can use the same name
  416 +to refer to its contents, which implies that the package name should
  417 +be good: short, concise, evocative. By convention, packages are
  418 +given lower case, single-word names; there should be no need for
  419 +underscores or mixedCaps. Err on the side of brevity, since everyone
  420 +using your package will be typing that name. And don't worry about
  421 +collisions a priori. The package name is only the default name for
  422 +imports; it need not be unique across all source code, and in the
  423 +rare case of a collision the importing package can choose a different
  424 +name to use locally. In any case, confusion is rare because the file
  425 +name in the import determines just which package is being used.
  426 +
  427 +### Avoid Renaming Imports
  428 +
  429 +Avoid renaming imports except to avoid a name collision; good package names
  430 +should not require renaming. In the event of collision, prefer to rename the
  431 +most local or project-specific import.
  432 +
  433 +### Context as First Argument
  434 +
  435 +Values of the context.Context type carry security credentials, tracing information,
  436 +deadlines, and cancellation signals across API and process boundaries. Go programs
  437 +pass Contexts explicitly along the entire function call chain from incoming RPCs
  438 +and HTTP requests to outgoing requests.
  439 +
  440 +Most functions that use a Context should accept it as their first parameter.
  441 +
  442 +### Do Not Discard Errors
  443 +
  444 +Do not discard errors using _ variables. If a function returns an error,
  445 +check it to make sure the function succeeded. Handle the error, return it, or,
  446 +in truly exceptional situations, panic.
  447 +
  448 +### Go Error Format
  449 +
  450 +Error strings should not be capitalized (unless beginning with proper nouns
  451 +or acronyms) or end with punctuation, since they are usually printed following
  452 +other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"),
  453 +so that log.Printf("Reading %s: %v", filename, err) formats without a spurious
  454 +capital letter mid-message. This does not apply to logging, which is implicitly
  455 +line-oriented and not combined inside other messages.
  456 +
  457 +### Use Crypto Rand
  458 +
  459 +Do not use package math/rand to generate keys, even
  460 +throwaway ones. Unseeded, the generator is completely predictable.
  461 +Seeded with time.Nanoseconds(), there are just a few bits of entropy.
  462 +Instead, use crypto/rand's Reader, and if you need text, print to
  463 +hexadecimal or base64
  1 +BSD 3-Clause License
  2 +
  3 +Copyright (c) 2016-2019, 360 Enterprise Security Group, Endpoint Security, Inc.
  4 +Copyright (c) 2011-2017, Geoffrey J. Teale (complying with the tealeg/xlsx license)
  5 +All rights reserved.
  6 +
  7 +Redistribution and use in source and binary forms, with or without
  8 +modification, are permitted provided that the following conditions are met:
  9 +
  10 +* Redistributions of source code must retain the above copyright notice, this
  11 + list of conditions and the following disclaimer.
  12 +
  13 +* Redistributions in binary form must reproduce the above copyright notice,
  14 + this list of conditions and the following disclaimer in the documentation
  15 + and/or other materials provided with the distribution.
  16 +
  17 +* Neither the name of the copyright holder nor the names of its
  18 + contributors may be used to endorse or promote products derived from
  19 + this software without specific prior written permission.
  20 +
  21 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  24 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  25 +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26 +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  27 +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28 +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29 +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  1 +# PR Details
  2 +
  3 +<!--- Provide a general summary of your changes in the Title above -->
  4 +
  5 +## Description
  6 +
  7 +<!--- Describe your changes in detail -->
  8 +
  9 +## Related Issue
  10 +
  11 +<!--- This project only accepts pull requests related to open issues -->
  12 +<!--- If suggesting a new feature or change, please discuss it in an issue first -->
  13 +<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
  14 +<!--- Please link to the issue here: -->
  15 +
  16 +## Motivation and Context
  17 +
  18 +<!--- Why is this change required? What problem does it solve? -->
  19 +
  20 +## How Has This Been Tested
  21 +
  22 +<!--- Please describe in detail how you tested your changes. -->
  23 +<!--- Include details of your testing environment, and the tests you ran to -->
  24 +<!--- see how your change affects other areas of the code, etc. -->
  25 +
  26 +## Types of changes
  27 +
  28 +<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
  29 +
  30 +- [ ] Docs change / refactoring / dependency upgrade
  31 +- [ ] Bug fix (non-breaking change which fixes an issue)
  32 +- [ ] New feature (non-breaking change which adds functionality)
  33 +- [ ] Breaking change (fix or feature that would cause existing functionality to change)
  34 +
  35 +## Checklist
  36 +
  37 +<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
  38 +<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
  39 +
  40 +- [ ] My code follows the code style of this project.
  41 +- [ ] My change requires a change to the documentation.
  42 +- [ ] I have updated the documentation accordingly.
  43 +- [ ] I have read the **CONTRIBUTING** document.
  44 +- [ ] I have added tests to cover my changes.
  45 +- [ ] All new and existing tests passed.
  1 +<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
  2 +
  3 +<p align="center">
  4 + <a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
  5 + <a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
  6 + <a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
  7 + <a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
  8 + <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
  9 + <a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
  10 +</p>
  11 +
  12 +# Excelize
  13 +
  14 +## Introduction
  15 +
  16 +Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX files. Supports reading and writing XLSX file generated by Microsoft Excel&trade; 2007 and later.
  17 +Supports saving a file without losing original charts of XLSX. This library needs Go version 1.10 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) and [docs reference](https://xuri.me/excelize/).
  18 +
  19 +## Basic Usage
  20 +
  21 +### Installation
  22 +
  23 +```bash
  24 +go get github.com/360EntSecGroup-Skylar/excelize
  25 +```
  26 +
  27 +### Create XLSX file
  28 +
  29 +Here is a minimal example usage that will create XLSX file.
  30 +
  31 +```go
  32 +package main
  33 +
  34 +import (
  35 + "fmt"
  36 +
  37 + "github.com/360EntSecGroup-Skylar/excelize"
  38 +)
  39 +
  40 +func main() {
  41 + f := excelize.NewFile()
  42 + // Create a new sheet.
  43 + index := f.NewSheet("Sheet2")
  44 + // Set value of a cell.
  45 + f.SetCellValue("Sheet2", "A2", "Hello world.")
  46 + f.SetCellValue("Sheet1", "B2", 100)
  47 + // Set active sheet of the workbook.
  48 + f.SetActiveSheet(index)
  49 + // Save xlsx file by the given path.
  50 + err := f.SaveAs("./Book1.xlsx")
  51 + if err != nil {
  52 + fmt.Println(err)
  53 + }
  54 +}
  55 +```
  56 +
  57 +### Reading XLSX file
  58 +
  59 +The following constitutes the bare to read a XLSX document.
  60 +
  61 +```go
  62 +package main
  63 +
  64 +import (
  65 + "fmt"
  66 +
  67 + "github.com/360EntSecGroup-Skylar/excelize"
  68 +)
  69 +
  70 +func main() {
  71 + f, err := excelize.OpenFile("./Book1.xlsx")
  72 + if err != nil {
  73 + fmt.Println(err)
  74 + return
  75 + }
  76 + // Get value from cell by given worksheet name and axis.
  77 + cell, err := f.GetCellValue("Sheet1", "B2")
  78 + if err != nil {
  79 + fmt.Println(err)
  80 + return
  81 + }
  82 + fmt.Println(cell)
  83 + // Get all the rows in the Sheet1.
  84 + rows, err := f.GetRows("Sheet1")
  85 + for _, row := range rows {
  86 + for _, colCell := range row {
  87 + fmt.Print(colCell, "\t")
  88 + }
  89 + fmt.Println()
  90 + }
  91 +}
  92 +```
  93 +
  94 +### Add chart to XLSX file
  95 +
  96 +With Excelize chart generation and management is as easy as a few lines of code. You can build charts based off data in your worksheet or generate charts without any data in your worksheet at all.
  97 +
  98 +<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
  99 +
  100 +```go
  101 +package main
  102 +
  103 +import (
  104 + "fmt"
  105 +
  106 + "github.com/360EntSecGroup-Skylar/excelize"
  107 +)
  108 +
  109 +func main() {
  110 + categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
  111 + values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
  112 + f := excelize.NewFile()
  113 + for k, v := range categories {
  114 + f.SetCellValue("Sheet1", k, v)
  115 + }
  116 + for k, v := range values {
  117 + f.SetCellValue("Sheet1", k, v)
  118 + }
  119 + err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
  120 + if err != nil {
  121 + fmt.Println(err)
  122 + return
  123 + }
  124 + // Save xlsx file by the given path.
  125 + err = f.SaveAs("./Book1.xlsx")
  126 + if err != nil {
  127 + fmt.Println(err)
  128 + }
  129 +}
  130 +```
  131 +
  132 +### Add picture to XLSX file
  133 +
  134 +```go
  135 +package main
  136 +
  137 +import (
  138 + "fmt"
  139 + _ "image/gif"
  140 + _ "image/jpeg"
  141 + _ "image/png"
  142 +
  143 + "github.com/360EntSecGroup-Skylar/excelize"
  144 +)
  145 +
  146 +func main() {
  147 + f, err := excelize.OpenFile("./Book1.xlsx")
  148 + if err != nil {
  149 + fmt.Println(err)
  150 + return
  151 + }
  152 + // Insert a picture.
  153 + err = f.AddPicture("Sheet1", "A2", "./image1.png", "")
  154 + if err != nil {
  155 + fmt.Println(err)
  156 + }
  157 + // Insert a picture to worksheet with scaling.
  158 + err = f.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
  159 + if err != nil {
  160 + fmt.Println(err)
  161 + }
  162 + // Insert a picture offset in the cell with printing support.
  163 + err = f.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
  164 + if err != nil {
  165 + fmt.Println(err)
  166 + }
  167 + // Save the xlsx file with the origin path.
  168 + err = f.Save()
  169 + if err != nil {
  170 + fmt.Println(err)
  171 + }
  172 +}
  173 +```
  174 +
  175 +## Contributing
  176 +
  177 +Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](http://www.ecma-international.org/publications/standards/Ecma-376.htm).
  178 +
  179 +## Licenses
  180 +
  181 +This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
  182 +
  183 +The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
  184 +
  185 +Some struct of XML originally by [tealeg/xlsx](https://github.com/tealeg/xlsx). Licensed under the [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE).
  186 +
  187 +gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/).
  1 +<p align="center"><img width="650" src="./excelize.svg" alt="Excelize logo"></p>
  2 +
  3 +<p align="center">
  4 + <a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
  5 + <a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
  6 + <a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
  7 + <a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
  8 + <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
  9 + <a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
  10 +</p>
  11 +
  12 +# Excelize
  13 +
  14 +## 简介
  15 +
  16 +Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。使用本类库要求使用的 Go 语言为 1.10 或更高版本,完整的 API 使用文档请访问 [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) 或查看 [参考文档](https://xuri.me/excelize/)
  17 +
  18 +## 快速上手
  19 +
  20 +### 安装
  21 +
  22 +```bash
  23 +go get github.com/360EntSecGroup-Skylar/excelize
  24 +```
  25 +
  26 +### 创建 Excel 文档
  27 +
  28 +下面是一个创建 Excel 文档的简单例子:
  29 +
  30 +```go
  31 +package main
  32 +
  33 +import (
  34 + "fmt"
  35 +
  36 + "github.com/360EntSecGroup-Skylar/excelize"
  37 +)
  38 +
  39 +func main() {
  40 + f := excelize.NewFile()
  41 + // 创建一个工作表
  42 + index := f.NewSheet("Sheet2")
  43 + // 设置单元格的值
  44 + f.SetCellValue("Sheet2", "A2", "Hello world.")
  45 + f.SetCellValue("Sheet1", "B2", 100)
  46 + // 设置工作簿的默认工作表
  47 + f.SetActiveSheet(index)
  48 + // 根据指定路径保存文件
  49 + err := f.SaveAs("./Book1.xlsx")
  50 + if err != nil {
  51 + fmt.Println(err)
  52 + }
  53 +}
  54 +```
  55 +
  56 +### 读取 Excel 文档
  57 +
  58 +下面是读取 Excel 文档的例子:
  59 +
  60 +```go
  61 +package main
  62 +
  63 +import (
  64 + "fmt"
  65 +
  66 + "github.com/360EntSecGroup-Skylar/excelize"
  67 +)
  68 +
  69 +func main() {
  70 + f, err := excelize.OpenFile("./Book1.xlsx")
  71 + if err != nil {
  72 + fmt.Println(err)
  73 + return
  74 + }
  75 + // 获取工作表中指定单元格的值
  76 + cell, err := f.GetCellValue("Sheet1", "B2")
  77 + if err != nil {
  78 + fmt.Println(err)
  79 + return
  80 + }
  81 + fmt.Println(cell)
  82 + // 获取 Sheet1 上所有单元格
  83 + rows, err := f.GetRows("Sheet1")
  84 + for _, row := range rows {
  85 + for _, colCell := range row {
  86 + fmt.Print(colCell, "\t")
  87 + }
  88 + fmt.Println()
  89 + }
  90 +}
  91 +```
  92 +
  93 +### 在 Excel 文档中创建图表
  94 +
  95 +使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。
  96 +
  97 +<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
  98 +
  99 +```go
  100 +package main
  101 +
  102 +import (
  103 + "fmt"
  104 +
  105 + "github.com/360EntSecGroup-Skylar/excelize"
  106 +)
  107 +
  108 +func main() {
  109 + categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
  110 + values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
  111 + f := excelize.NewFile()
  112 + for k, v := range categories {
  113 + f.SetCellValue("Sheet1", k, v)
  114 + }
  115 + for k, v := range values {
  116 + f.SetCellValue("Sheet1", k, v)
  117 + }
  118 + err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
  119 + if err != nil {
  120 + fmt.Println(err)
  121 + return
  122 + }
  123 + // 根据指定路径保存文件
  124 + err = f.SaveAs("./Book1.xlsx")
  125 + if err != nil {
  126 + fmt.Println(err)
  127 + }
  128 +}
  129 +
  130 +```
  131 +
  132 +### 向 Excel 文档中插入图片
  133 +
  134 +```go
  135 +package main
  136 +
  137 +import (
  138 + "fmt"
  139 + _ "image/gif"
  140 + _ "image/jpeg"
  141 + _ "image/png"
  142 +
  143 + "github.com/360EntSecGroup-Skylar/excelize"
  144 +)
  145 +
  146 +func main() {
  147 + f, err := excelize.OpenFile("./Book1.xlsx")
  148 + if err != nil {
  149 + fmt.Println(err)
  150 + return
  151 + }
  152 + // 插入图片
  153 + err = f.AddPicture("Sheet1", "A2", "./image1.png", "")
  154 + if err != nil {
  155 + fmt.Println(err)
  156 + }
  157 + // 在工作表中插入图片,并设置图片的缩放比例
  158 + err = f.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
  159 + if err != nil {
  160 + fmt.Println(err)
  161 + }
  162 + // 在工作表中插入图片,并设置图片的打印属性
  163 + err = f.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
  164 + if err != nil {
  165 + fmt.Println(err)
  166 + }
  167 + // 保存文件
  168 + err = f.Save()
  169 + if err != nil {
  170 + fmt.Println(err)
  171 + }
  172 +}
  173 +```
  174 +
  175 +## 社区合作
  176 +
  177 +欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](http://www.ecma-international.org/publications/standards/Ecma-376.htm)
  178 +
  179 +## 开源许可
  180 +
  181 +本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。
  182 +
  183 +Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
  184 +
  185 +本类库中部分 XML 结构体的定义参考了开源项目:[tealeg/xlsx](https://github.com/tealeg/xlsx),遵循 [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE) 开源许可协议。
  186 +
  187 +gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。
  1 +# Security Policy
  2 +
  3 +## Supported Versions
  4 +
  5 +We will dive into any security-related issue as long as your Excelize version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information.
  6 +
  7 +## Reporting a Vulnerability
  8 +
  9 +Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you!
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import (
  13 + "errors"
  14 + "strings"
  15 +)
  16 +
  17 +type adjustDirection bool
  18 +
  19 +const (
  20 + columns adjustDirection = false
  21 + rows adjustDirection = true
  22 +)
  23 +
  24 +// adjustHelper provides a function to adjust rows and columns dimensions,
  25 +// hyperlinks, merged cells and auto filter when inserting or deleting rows or
  26 +// columns.
  27 +//
  28 +// sheet: Worksheet name that we're editing
  29 +// column: Index number of the column we're inserting/deleting before
  30 +// row: Index number of the row we're inserting/deleting before
  31 +// offset: Number of rows/column to insert/delete negative values indicate deletion
  32 +//
  33 +// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
  34 +//
  35 +func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
  36 + xlsx, err := f.workSheetReader(sheet)
  37 + if err != nil {
  38 + return err
  39 + }
  40 + if dir == rows {
  41 + f.adjustRowDimensions(xlsx, num, offset)
  42 + } else {
  43 + f.adjustColDimensions(xlsx, num, offset)
  44 + }
  45 + f.adjustHyperlinks(xlsx, sheet, dir, num, offset)
  46 + if err = f.adjustMergeCells(xlsx, dir, num, offset); err != nil {
  47 + return err
  48 + }
  49 + if err = f.adjustAutoFilter(xlsx, dir, num, offset); err != nil {
  50 + return err
  51 + }
  52 + if err = f.adjustCalcChain(dir, num, offset); err != nil {
  53 + return err
  54 + }
  55 + checkSheet(xlsx)
  56 + checkRow(xlsx)
  57 +
  58 + if xlsx.MergeCells != nil && len(xlsx.MergeCells.Cells) == 0 {
  59 + xlsx.MergeCells = nil
  60 + }
  61 +
  62 + return nil
  63 +}
  64 +
  65 +// adjustColDimensions provides a function to update column dimensions when
  66 +// inserting or deleting rows or columns.
  67 +func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) {
  68 + for rowIdx := range xlsx.SheetData.Row {
  69 + for colIdx, v := range xlsx.SheetData.Row[rowIdx].C {
  70 + cellCol, cellRow, _ := CellNameToCoordinates(v.R)
  71 + if col <= cellCol {
  72 + if newCol := cellCol + offset; newCol > 0 {
  73 + xlsx.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
  74 + }
  75 + }
  76 + }
  77 + }
  78 +}
  79 +
  80 +// adjustRowDimensions provides a function to update row dimensions when
  81 +// inserting or deleting rows or columns.
  82 +func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) {
  83 + for i, r := range xlsx.SheetData.Row {
  84 + if newRow := r.R + offset; r.R >= row && newRow > 0 {
  85 + f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], newRow)
  86 + }
  87 + }
  88 +}
  89 +
  90 +// ajustSingleRowDimensions provides a function to ajust single row dimensions.
  91 +func (f *File) ajustSingleRowDimensions(r *xlsxRow, num int) {
  92 + r.R = num
  93 + for i, col := range r.C {
  94 + colName, _, _ := SplitCellName(col.R)
  95 + r.C[i].R, _ = JoinCellName(colName, num)
  96 + }
  97 +}
  98 +
  99 +// adjustHyperlinks provides a function to update hyperlinks when inserting or
  100 +// deleting rows or columns.
  101 +func (f *File) adjustHyperlinks(xlsx *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
  102 + // short path
  103 + if xlsx.Hyperlinks == nil || len(xlsx.Hyperlinks.Hyperlink) == 0 {
  104 + return
  105 + }
  106 +
  107 + // order is important
  108 + if offset < 0 {
  109 + for rowIdx, linkData := range xlsx.Hyperlinks.Hyperlink {
  110 + colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
  111 +
  112 + if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
  113 + f.deleteSheetRelationships(sheet, linkData.RID)
  114 + if len(xlsx.Hyperlinks.Hyperlink) > 1 {
  115 + xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:rowIdx],
  116 + xlsx.Hyperlinks.Hyperlink[rowIdx+1:]...)
  117 + } else {
  118 + xlsx.Hyperlinks = nil
  119 + }
  120 + }
  121 + }
  122 + }
  123 +
  124 + if xlsx.Hyperlinks == nil {
  125 + return
  126 + }
  127 +
  128 + for i := range xlsx.Hyperlinks.Hyperlink {
  129 + link := &xlsx.Hyperlinks.Hyperlink[i] // get reference
  130 + colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
  131 +
  132 + if dir == rows {
  133 + if rowNum >= num {
  134 + link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
  135 + }
  136 + } else {
  137 + if colNum >= num {
  138 + link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
  139 + }
  140 + }
  141 + }
  142 +}
  143 +
  144 +// adjustAutoFilter provides a function to update the auto filter when
  145 +// inserting or deleting rows or columns.
  146 +func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error {
  147 + if xlsx.AutoFilter == nil {
  148 + return nil
  149 + }
  150 +
  151 + coordinates, err := f.areaRefToCoordinates(xlsx.AutoFilter.Ref)
  152 + if err != nil {
  153 + return err
  154 + }
  155 + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  156 +
  157 + if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
  158 + xlsx.AutoFilter = nil
  159 + for rowIdx := range xlsx.SheetData.Row {
  160 + rowData := &xlsx.SheetData.Row[rowIdx]
  161 + if rowData.R > y1 && rowData.R <= y2 {
  162 + rowData.Hidden = false
  163 + }
  164 + }
  165 + return nil
  166 + }
  167 +
  168 + coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
  169 + x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  170 +
  171 + if xlsx.AutoFilter.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
  172 + return err
  173 + }
  174 + return nil
  175 +}
  176 +
  177 +// adjustAutoFilterHelper provides a function for adjusting auto filter to
  178 +// compare and calculate cell axis by the given adjust direction, operation
  179 +// axis and offset.
  180 +func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
  181 + if dir == rows {
  182 + if coordinates[1] >= num {
  183 + coordinates[1] += offset
  184 + }
  185 + if coordinates[3] >= num {
  186 + coordinates[3] += offset
  187 + }
  188 + } else {
  189 + if coordinates[2] >= num {
  190 + coordinates[2] += offset
  191 + }
  192 + }
  193 + return coordinates
  194 +}
  195 +
  196 +// areaRefToCoordinates provides a function to convert area reference to a
  197 +// pair of coordinates.
  198 +func (f *File) areaRefToCoordinates(ref string) ([]int, error) {
  199 + coordinates := make([]int, 4)
  200 + rng := strings.Split(ref, ":")
  201 + firstCell := rng[0]
  202 + lastCell := rng[1]
  203 + var err error
  204 + coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell)
  205 + if err != nil {
  206 + return coordinates, err
  207 + }
  208 + coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell)
  209 + if err != nil {
  210 + return coordinates, err
  211 + }
  212 + return coordinates, err
  213 +}
  214 +
  215 +// coordinatesToAreaRef provides a function to convert a pair of coordinates
  216 +// to area reference.
  217 +func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
  218 + if len(coordinates) != 4 {
  219 + return "", errors.New("coordinates length must be 4")
  220 + }
  221 + firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1])
  222 + if err != nil {
  223 + return "", err
  224 + }
  225 + lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3])
  226 + if err != nil {
  227 + return "", err
  228 + }
  229 + return firstCell + ":" + lastCell, err
  230 +}
  231 +
  232 +// adjustMergeCells provides a function to update merged cells when inserting
  233 +// or deleting rows or columns.
  234 +func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error {
  235 + if xlsx.MergeCells == nil {
  236 + return nil
  237 + }
  238 +
  239 + for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
  240 + areaData := xlsx.MergeCells.Cells[i]
  241 + coordinates, err := f.areaRefToCoordinates(areaData.Ref)
  242 + if err != nil {
  243 + return err
  244 + }
  245 + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  246 + if dir == rows {
  247 + if y1 == num && y2 == num && offset < 0 {
  248 + f.deleteMergeCell(xlsx, i)
  249 + i--
  250 + }
  251 + y1 = f.adjustMergeCellsHelper(y1, num, offset)
  252 + y2 = f.adjustMergeCellsHelper(y2, num, offset)
  253 + } else {
  254 + if x1 == num && x2 == num && offset < 0 {
  255 + f.deleteMergeCell(xlsx, i)
  256 + i--
  257 + }
  258 + x1 = f.adjustMergeCellsHelper(x1, num, offset)
  259 + x2 = f.adjustMergeCellsHelper(x2, num, offset)
  260 + }
  261 + if x1 == x2 && y1 == y2 {
  262 + f.deleteMergeCell(xlsx, i)
  263 + i--
  264 + }
  265 + if areaData.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
  266 + return err
  267 + }
  268 + }
  269 + return nil
  270 +}
  271 +
  272 +// adjustMergeCellsHelper provides a function for adjusting merge cells to
  273 +// compare and calculate cell axis by the given pivot, operation axis and
  274 +// offset.
  275 +func (f *File) adjustMergeCellsHelper(pivot, num, offset int) int {
  276 + if pivot >= num {
  277 + pivot += offset
  278 + if pivot < 1 {
  279 + return 1
  280 + }
  281 + return pivot
  282 + }
  283 + return pivot
  284 +}
  285 +
  286 +// deleteMergeCell provides a function to delete merged cell by given index.
  287 +func (f *File) deleteMergeCell(sheet *xlsxWorksheet, idx int) {
  288 + if len(sheet.MergeCells.Cells) > idx {
  289 + sheet.MergeCells.Cells = append(sheet.MergeCells.Cells[:idx], sheet.MergeCells.Cells[idx+1:]...)
  290 + sheet.MergeCells.Count = len(sheet.MergeCells.Cells)
  291 + }
  292 +}
  293 +
  294 +// adjustCalcChain provides a function to update the calculation chain when
  295 +// inserting or deleting rows or columns.
  296 +func (f *File) adjustCalcChain(dir adjustDirection, num, offset int) error {
  297 + if f.CalcChain == nil {
  298 + return nil
  299 + }
  300 + for index, c := range f.CalcChain.C {
  301 + colNum, rowNum, err := CellNameToCoordinates(c.R)
  302 + if err != nil {
  303 + return err
  304 + }
  305 + if dir == rows && num <= rowNum {
  306 + if newRow := rowNum + offset; newRow > 0 {
  307 + f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow)
  308 + }
  309 + }
  310 + if dir == columns && num <= colNum {
  311 + if newCol := colNum + offset; newCol > 0 {
  312 + f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum)
  313 + }
  314 + }
  315 + }
  316 + return nil
  317 +}
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import "encoding/xml"
  13 +
  14 +// calcChainReader provides a function to get the pointer to the structure
  15 +// after deserialization of xl/calcChain.xml.
  16 +func (f *File) calcChainReader() *xlsxCalcChain {
  17 + if f.CalcChain == nil {
  18 + var c xlsxCalcChain
  19 + _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")), &c)
  20 + f.CalcChain = &c
  21 + }
  22 + return f.CalcChain
  23 +}
  24 +
  25 +// calcChainWriter provides a function to save xl/calcChain.xml after
  26 +// serialize structure.
  27 +func (f *File) calcChainWriter() {
  28 + if f.CalcChain != nil && f.CalcChain.C != nil {
  29 + output, _ := xml.Marshal(f.CalcChain)
  30 + f.saveFileList("xl/calcChain.xml", output)
  31 + }
  32 +}
  33 +
  34 +// deleteCalcChain provides a function to remove cell reference on the
  35 +// calculation chain.
  36 +func (f *File) deleteCalcChain(index int, axis string) {
  37 + calc := f.calcChainReader()
  38 + if calc != nil {
  39 + calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool {
  40 + return !((c.I == index && c.R == axis) || (c.I == index && axis == ""))
  41 + })
  42 + }
  43 + if len(calc.C) == 0 {
  44 + f.CalcChain = nil
  45 + delete(f.XLSX, "xl/calcChain.xml")
  46 + content := f.contentTypesReader()
  47 + for k, v := range content.Overrides {
  48 + if v.PartName == "/xl/calcChain.xml" {
  49 + content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
  50 + }
  51 + }
  52 + }
  53 +}
  54 +
  55 +type xlsxCalcChainCollection []xlsxCalcChainC
  56 +
  57 +// Filter provides a function to filter calculation chain.
  58 +func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC {
  59 + results := make([]xlsxCalcChainC, 0)
  60 + for _, v := range c {
  61 + if fn(v) {
  62 + results = append(results, v)
  63 + }
  64 + }
  65 + return results
  66 +}
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import (
  13 + "encoding/xml"
  14 + "errors"
  15 + "fmt"
  16 + "reflect"
  17 + "strconv"
  18 + "strings"
  19 + "time"
  20 +)
  21 +
  22 +const (
  23 + // STCellFormulaTypeArray defined the formula is an array formula.
  24 + STCellFormulaTypeArray = "array"
  25 + // STCellFormulaTypeDataTable defined the formula is a data table formula.
  26 + STCellFormulaTypeDataTable = "dataTable"
  27 + // STCellFormulaTypeNormal defined the formula is a regular cell formula.
  28 + STCellFormulaTypeNormal = "normal"
  29 + // STCellFormulaTypeShared defined the formula is part of a shared formula.
  30 + STCellFormulaTypeShared = "shared"
  31 +)
  32 +
  33 +// GetCellValue provides a function to get formatted value from cell by given
  34 +// worksheet name and axis in XLSX file. If it is possible to apply a format
  35 +// to the cell value, it will do so, if not then an error will be returned,
  36 +// along with the raw value of the cell.
  37 +func (f *File) GetCellValue(sheet, axis string) (string, error) {
  38 + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
  39 + val, err := c.getValueFrom(f, f.sharedStringsReader())
  40 + if err != nil {
  41 + return val, false, err
  42 + }
  43 + return val, true, err
  44 + })
  45 +}
  46 +
  47 +// SetCellValue provides a function to set value of a cell. The following
  48 +// shows the supported data types:
  49 +//
  50 +// int
  51 +// int8
  52 +// int16
  53 +// int32
  54 +// int64
  55 +// uint
  56 +// uint8
  57 +// uint16
  58 +// uint32
  59 +// uint64
  60 +// float32
  61 +// float64
  62 +// string
  63 +// []byte
  64 +// time.Duration
  65 +// time.Time
  66 +// bool
  67 +// nil
  68 +//
  69 +// Note that default date format is m/d/yy h:mm of time.Time type value. You can
  70 +// set numbers format by SetCellStyle() method.
  71 +func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
  72 + var err error
  73 + switch v := value.(type) {
  74 + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
  75 + err = f.setCellIntFunc(sheet, axis, v)
  76 + case float32:
  77 + err = f.SetCellFloat(sheet, axis, float64(v), -1, 32)
  78 + case float64:
  79 + err = f.SetCellFloat(sheet, axis, v, -1, 64)
  80 + case string:
  81 + err = f.SetCellStr(sheet, axis, v)
  82 + case []byte:
  83 + err = f.SetCellStr(sheet, axis, string(v))
  84 + case time.Duration:
  85 + err = f.SetCellDefault(sheet, axis, strconv.FormatFloat(v.Seconds()/86400.0, 'f', -1, 32))
  86 + if err != nil {
  87 + return err
  88 + }
  89 + err = f.setDefaultTimeStyle(sheet, axis, 21)
  90 + case time.Time:
  91 + err = f.setCellTimeFunc(sheet, axis, v)
  92 + case bool:
  93 + err = f.SetCellBool(sheet, axis, v)
  94 + case nil:
  95 + err = f.SetCellStr(sheet, axis, "")
  96 + default:
  97 + err = f.SetCellStr(sheet, axis, fmt.Sprint(value))
  98 + }
  99 + return err
  100 +}
  101 +
  102 +// setCellIntFunc is a wrapper of SetCellInt.
  103 +func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
  104 + var err error
  105 + switch v := value.(type) {
  106 + case int:
  107 + err = f.SetCellInt(sheet, axis, v)
  108 + case int8:
  109 + err = f.SetCellInt(sheet, axis, int(v))
  110 + case int16:
  111 + err = f.SetCellInt(sheet, axis, int(v))
  112 + case int32:
  113 + err = f.SetCellInt(sheet, axis, int(v))
  114 + case int64:
  115 + err = f.SetCellInt(sheet, axis, int(v))
  116 + case uint:
  117 + err = f.SetCellInt(sheet, axis, int(v))
  118 + case uint8:
  119 + err = f.SetCellInt(sheet, axis, int(v))
  120 + case uint16:
  121 + err = f.SetCellInt(sheet, axis, int(v))
  122 + case uint32:
  123 + err = f.SetCellInt(sheet, axis, int(v))
  124 + case uint64:
  125 + err = f.SetCellInt(sheet, axis, int(v))
  126 + }
  127 + return err
  128 +}
  129 +
  130 +// setCellTimeFunc provides a method to process time type of value for
  131 +// SetCellValue.
  132 +func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
  133 + excelTime, err := timeToExcelTime(value)
  134 + if err != nil {
  135 + return err
  136 + }
  137 + if excelTime > 0 {
  138 + err = f.SetCellDefault(sheet, axis, strconv.FormatFloat(excelTime, 'f', -1, 64))
  139 + if err != nil {
  140 + return err
  141 + }
  142 + err = f.setDefaultTimeStyle(sheet, axis, 22)
  143 + if err != nil {
  144 + return err
  145 + }
  146 + } else {
  147 + err = f.SetCellStr(sheet, axis, value.Format(time.RFC3339Nano))
  148 + if err != nil {
  149 + return err
  150 + }
  151 + }
  152 + return err
  153 +}
  154 +
  155 +// SetCellInt provides a function to set int type value of a cell by given
  156 +// worksheet name, cell coordinates and cell value.
  157 +func (f *File) SetCellInt(sheet, axis string, value int) error {
  158 + xlsx, err := f.workSheetReader(sheet)
  159 + if err != nil {
  160 + return err
  161 + }
  162 + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
  163 + if err != nil {
  164 + return err
  165 + }
  166 + cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
  167 + cellData.T = ""
  168 + cellData.V = strconv.Itoa(value)
  169 + return err
  170 +}
  171 +
  172 +// SetCellBool provides a function to set bool type value of a cell by given
  173 +// worksheet name, cell name and cell value.
  174 +func (f *File) SetCellBool(sheet, axis string, value bool) error {
  175 + xlsx, err := f.workSheetReader(sheet)
  176 + if err != nil {
  177 + return err
  178 + }
  179 + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
  180 + if err != nil {
  181 + return err
  182 + }
  183 + cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
  184 + cellData.T = "b"
  185 + if value {
  186 + cellData.V = "1"
  187 + } else {
  188 + cellData.V = "0"
  189 + }
  190 + return err
  191 +}
  192 +
  193 +// SetCellFloat sets a floating point value into a cell. The prec parameter
  194 +// specifies how many places after the decimal will be shown while -1 is a
  195 +// special value that will use as many decimal places as necessary to
  196 +// represent the number. bitSize is 32 or 64 depending on if a float32 or
  197 +// float64 was originally used for the value. For Example:
  198 +//
  199 +// var x float32 = 1.325
  200 +// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
  201 +//
  202 +func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error {
  203 + xlsx, err := f.workSheetReader(sheet)
  204 + if err != nil {
  205 + return err
  206 + }
  207 + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
  208 + if err != nil {
  209 + return err
  210 + }
  211 + cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
  212 + cellData.T = ""
  213 + cellData.V = strconv.FormatFloat(value, 'f', prec, bitSize)
  214 + return err
  215 +}
  216 +
  217 +// SetCellStr provides a function to set string type value of a cell. Total
  218 +// number of characters that a cell can contain 32767 characters.
  219 +func (f *File) SetCellStr(sheet, axis, value string) error {
  220 + xlsx, err := f.workSheetReader(sheet)
  221 + if err != nil {
  222 + return err
  223 + }
  224 + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
  225 + if err != nil {
  226 + return err
  227 + }
  228 + if len(value) > 32767 {
  229 + value = value[0:32767]
  230 + }
  231 + // Leading space(s) character detection.
  232 + if len(value) > 0 && value[0] == 32 {
  233 + cellData.XMLSpace = xml.Attr{
  234 + Name: xml.Name{Space: NameSpaceXML, Local: "space"},
  235 + Value: "preserve",
  236 + }
  237 + }
  238 +
  239 + cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
  240 + cellData.T = "str"
  241 + cellData.V = value
  242 + return err
  243 +}
  244 +
  245 +// SetCellDefault provides a function to set string type value of a cell as
  246 +// default format without escaping the cell.
  247 +func (f *File) SetCellDefault(sheet, axis, value string) error {
  248 + xlsx, err := f.workSheetReader(sheet)
  249 + if err != nil {
  250 + return err
  251 + }
  252 + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
  253 + if err != nil {
  254 + return err
  255 + }
  256 + cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
  257 + cellData.T = ""
  258 + cellData.V = value
  259 + return err
  260 +}
  261 +
  262 +// GetCellFormula provides a function to get formula from cell by given
  263 +// worksheet name and axis in XLSX file.
  264 +func (f *File) GetCellFormula(sheet, axis string) (string, error) {
  265 + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
  266 + if c.F == nil {
  267 + return "", false, nil
  268 + }
  269 + if c.F.T == STCellFormulaTypeShared {
  270 + return getSharedForumula(x, c.F.Si), true, nil
  271 + }
  272 + return c.F.Content, true, nil
  273 + })
  274 +}
  275 +
  276 +// FormulaOpts can be passed to SetCellFormula to use other formula types.
  277 +type FormulaOpts struct {
  278 + Type *string // Formula type
  279 + Ref *string // Shared formula ref
  280 +}
  281 +
  282 +// SetCellFormula provides a function to set cell formula by given string and
  283 +// worksheet name.
  284 +func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
  285 + xlsx, err := f.workSheetReader(sheet)
  286 + if err != nil {
  287 + return err
  288 + }
  289 + cellData, _, _, err := f.prepareCell(xlsx, sheet, axis)
  290 + if err != nil {
  291 + return err
  292 + }
  293 + if formula == "" {
  294 + cellData.F = nil
  295 + f.deleteCalcChain(f.GetSheetIndex(sheet), axis)
  296 + return err
  297 + }
  298 +
  299 + if cellData.F != nil {
  300 + cellData.F.Content = formula
  301 + } else {
  302 + cellData.F = &xlsxF{Content: formula}
  303 + }
  304 +
  305 + for _, o := range opts {
  306 + if o.Type != nil {
  307 + cellData.F.T = *o.Type
  308 + }
  309 +
  310 + if o.Ref != nil {
  311 + cellData.F.Ref = *o.Ref
  312 + }
  313 + }
  314 +
  315 + return err
  316 +}
  317 +
  318 +// GetCellHyperLink provides a function to get cell hyperlink by given
  319 +// worksheet name and axis. Boolean type value link will be ture if the cell
  320 +// has a hyperlink and the target is the address of the hyperlink. Otherwise,
  321 +// the value of link will be false and the value of the target will be a blank
  322 +// string. For example get hyperlink of Sheet1!H6:
  323 +//
  324 +// link, target, err := f.GetCellHyperLink("Sheet1", "H6")
  325 +//
  326 +func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) {
  327 + // Check for correct cell name
  328 + if _, _, err := SplitCellName(axis); err != nil {
  329 + return false, "", err
  330 + }
  331 +
  332 + xlsx, err := f.workSheetReader(sheet)
  333 + if err != nil {
  334 + return false, "", err
  335 + }
  336 + axis, err = f.mergeCellsParser(xlsx, axis)
  337 + if err != nil {
  338 + return false, "", err
  339 + }
  340 + if xlsx.Hyperlinks != nil {
  341 + for _, link := range xlsx.Hyperlinks.Hyperlink {
  342 + if link.Ref == axis {
  343 + if link.RID != "" {
  344 + return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err
  345 + }
  346 + return true, link.Location, err
  347 + }
  348 + }
  349 + }
  350 + return false, "", err
  351 +}
  352 +
  353 +// SetCellHyperLink provides a function to set cell hyperlink by given
  354 +// worksheet name and link URL address. LinkType defines two types of
  355 +// hyperlink "External" for web site or "Location" for moving to one of cell
  356 +// in this workbook. Maximum limit hyperlinks in a worksheet is 65530. The
  357 +// below is example for external link.
  358 +//
  359 +// err := f.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
  360 +// // Set underline and font color style for the cell.
  361 +// style, err := f.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
  362 +// err = f.SetCellStyle("Sheet1", "A3", "A3", style)
  363 +//
  364 +// A this is another example for "Location":
  365 +//
  366 +// err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
  367 +//
  368 +func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
  369 + // Check for correct cell name
  370 + if _, _, err := SplitCellName(axis); err != nil {
  371 + return err
  372 + }
  373 +
  374 + xlsx, err := f.workSheetReader(sheet)
  375 + if err != nil {
  376 + return err
  377 + }
  378 + axis, err = f.mergeCellsParser(xlsx, axis)
  379 + if err != nil {
  380 + return err
  381 + }
  382 +
  383 + var linkData xlsxHyperlink
  384 +
  385 + if xlsx.Hyperlinks == nil {
  386 + xlsx.Hyperlinks = new(xlsxHyperlinks)
  387 + }
  388 +
  389 + if len(xlsx.Hyperlinks.Hyperlink) > 65529 {
  390 + return errors.New("over maximum limit hyperlinks in a worksheet")
  391 + }
  392 +
  393 + switch linkType {
  394 + case "External":
  395 + linkData = xlsxHyperlink{
  396 + Ref: axis,
  397 + }
  398 + sheetPath, _ := f.sheetMap[trimSheetName(sheet)]
  399 + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
  400 + rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType)
  401 + linkData.RID = "rId" + strconv.Itoa(rID)
  402 + case "Location":
  403 + linkData = xlsxHyperlink{
  404 + Ref: axis,
  405 + Location: link,
  406 + }
  407 + default:
  408 + return fmt.Errorf("invalid link type %q", linkType)
  409 + }
  410 +
  411 + xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData)
  412 + return nil
  413 +}
  414 +
  415 +// MergeCell provides a function to merge cells by given coordinate area and
  416 +// sheet name. For example create a merged cell of D3:E9 on Sheet1:
  417 +//
  418 +// err := f.MergeCell("Sheet1", "D3", "E9")
  419 +//
  420 +// If you create a merged cell that overlaps with another existing merged cell,
  421 +// those merged cells that already exist will be removed.
  422 +func (f *File) MergeCell(sheet, hcell, vcell string) error {
  423 + coordinates, err := f.areaRefToCoordinates(hcell + ":" + vcell)
  424 + if err != nil {
  425 + return err
  426 + }
  427 + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  428 +
  429 + if x1 == x2 && y1 == y2 {
  430 + return err
  431 + }
  432 +
  433 + // Correct the coordinate area, such correct C1:B3 to B1:C3.
  434 + if x2 < x1 {
  435 + x1, x2 = x2, x1
  436 + }
  437 +
  438 + if y2 < y1 {
  439 + y1, y2 = y2, y1
  440 + }
  441 +
  442 + hcell, _ = CoordinatesToCellName(x1, y1)
  443 + vcell, _ = CoordinatesToCellName(x2, y2)
  444 +
  445 + xlsx, err := f.workSheetReader(sheet)
  446 + if err != nil {
  447 + return err
  448 + }
  449 + if xlsx.MergeCells != nil {
  450 + ref := hcell + ":" + vcell
  451 + // Delete the merged cells of the overlapping area.
  452 + for _, cellData := range xlsx.MergeCells.Cells {
  453 + cc := strings.Split(cellData.Ref, ":")
  454 + if len(cc) != 2 {
  455 + return fmt.Errorf("invalid area %q", cellData.Ref)
  456 + }
  457 + c1, _ := checkCellInArea(hcell, cellData.Ref)
  458 + c2, _ := checkCellInArea(vcell, cellData.Ref)
  459 + c3, _ := checkCellInArea(cc[0], ref)
  460 + c4, _ := checkCellInArea(cc[1], ref)
  461 + if !(!c1 && !c2 && !c3 && !c4) {
  462 + return nil
  463 + }
  464 + }
  465 + xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
  466 + } else {
  467 + xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}}
  468 + }
  469 + return err
  470 +}
  471 +
  472 +// SetSheetRow writes an array to row by given worksheet name, starting
  473 +// coordinate and a pointer to array type 'slice'. For example, writes an
  474 +// array to row 6 start with the cell B6 on Sheet1:
  475 +//
  476 +// err := f.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2})
  477 +//
  478 +func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
  479 + col, row, err := CellNameToCoordinates(axis)
  480 + if err != nil {
  481 + return err
  482 + }
  483 +
  484 + // Make sure 'slice' is a Ptr to Slice
  485 + v := reflect.ValueOf(slice)
  486 + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
  487 + return errors.New("pointer to slice expected")
  488 + }
  489 + v = v.Elem()
  490 +
  491 + for i := 0; i < v.Len(); i++ {
  492 + cell, err := CoordinatesToCellName(col+i, row)
  493 + // Error should never happens here. But keep checking to early detect regresions
  494 + // if it will be introduced in future.
  495 + if err != nil {
  496 + return err
  497 + }
  498 + if err := f.SetCellValue(sheet, cell, v.Index(i).Interface()); err != nil {
  499 + return err
  500 + }
  501 + }
  502 + return err
  503 +}
  504 +
  505 +// getCellInfo does common preparation for all SetCell* methods.
  506 +func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
  507 + var err error
  508 + cell, err = f.mergeCellsParser(xlsx, cell)
  509 + if err != nil {
  510 + return nil, 0, 0, err
  511 + }
  512 + col, row, err := CellNameToCoordinates(cell)
  513 + if err != nil {
  514 + return nil, 0, 0, err
  515 + }
  516 +
  517 + prepareSheetXML(xlsx, col, row)
  518 +
  519 + return &xlsx.SheetData.Row[row-1].C[col-1], col, row, err
  520 +}
  521 +
  522 +// getCellStringFunc does common value extraction workflow for all GetCell*
  523 +// methods. Passed function implements specific part of required logic.
  524 +func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) {
  525 + xlsx, err := f.workSheetReader(sheet)
  526 + if err != nil {
  527 + return "", err
  528 + }
  529 + axis, err = f.mergeCellsParser(xlsx, axis)
  530 + if err != nil {
  531 + return "", err
  532 + }
  533 + _, row, err := CellNameToCoordinates(axis)
  534 + if err != nil {
  535 + return "", err
  536 + }
  537 +
  538 + lastRowNum := 0
  539 + if l := len(xlsx.SheetData.Row); l > 0 {
  540 + lastRowNum = xlsx.SheetData.Row[l-1].R
  541 + }
  542 +
  543 + // keep in mind: row starts from 1
  544 + if row > lastRowNum {
  545 + return "", nil
  546 + }
  547 +
  548 + for rowIdx := range xlsx.SheetData.Row {
  549 + rowData := &xlsx.SheetData.Row[rowIdx]
  550 + if rowData.R != row {
  551 + continue
  552 + }
  553 + for colIdx := range rowData.C {
  554 + colData := &rowData.C[colIdx]
  555 + if axis != colData.R {
  556 + continue
  557 + }
  558 + val, ok, err := fn(xlsx, colData)
  559 + if err != nil {
  560 + return "", err
  561 + }
  562 + if ok {
  563 + return val, nil
  564 + }
  565 + }
  566 + }
  567 + return "", nil
  568 +}
  569 +
  570 +// formattedValue provides a function to returns a value after formatted. If
  571 +// it is possible to apply a format to the cell value, it will do so, if not
  572 +// then an error will be returned, along with the raw value of the cell.
  573 +func (f *File) formattedValue(s int, v string) string {
  574 + if s == 0 {
  575 + return v
  576 + }
  577 + styleSheet := f.stylesReader()
  578 + ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
  579 + if ok != nil {
  580 + return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
  581 + }
  582 + return v
  583 +}
  584 +
  585 +// prepareCellStyle provides a function to prepare style index of cell in
  586 +// worksheet by given column index and style index.
  587 +func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
  588 + if xlsx.Cols != nil && style == 0 {
  589 + for _, c := range xlsx.Cols.Col {
  590 + if c.Min <= col && col <= c.Max {
  591 + style = c.Style
  592 + }
  593 + }
  594 + }
  595 + return style
  596 +}
  597 +
  598 +// mergeCellsParser provides a function to check merged cells in worksheet by
  599 +// given axis.
  600 +func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) (string, error) {
  601 + axis = strings.ToUpper(axis)
  602 + if xlsx.MergeCells != nil {
  603 + for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
  604 + ok, err := checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref)
  605 + if err != nil {
  606 + return axis, err
  607 + }
  608 + if ok {
  609 + axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
  610 + }
  611 + }
  612 + }
  613 + return axis, nil
  614 +}
  615 +
  616 +// checkCellInArea provides a function to determine if a given coordinate is
  617 +// within an area.
  618 +func checkCellInArea(cell, area string) (bool, error) {
  619 + col, row, err := CellNameToCoordinates(cell)
  620 + if err != nil {
  621 + return false, err
  622 + }
  623 +
  624 + rng := strings.Split(area, ":")
  625 + if len(rng) != 2 {
  626 + return false, err
  627 + }
  628 +
  629 + firstCol, firstRow, _ := CellNameToCoordinates(rng[0])
  630 + lastCol, lastRow, _ := CellNameToCoordinates(rng[1])
  631 +
  632 + return col >= firstCol && col <= lastCol && row >= firstRow && row <= lastRow, err
  633 +}
  634 +
  635 +// getSharedForumula find a cell contains the same formula as another cell,
  636 +// the "shared" value can be used for the t attribute and the si attribute can
  637 +// be used to refer to the cell containing the formula. Two formulas are
  638 +// considered to be the same when their respective representations in
  639 +// R1C1-reference notation, are the same.
  640 +//
  641 +// Note that this function not validate ref tag to check the cell if or not in
  642 +// allow area, and always return origin shared formula.
  643 +func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
  644 + for _, r := range xlsx.SheetData.Row {
  645 + for _, c := range r.C {
  646 + if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si == si {
  647 + return c.F.Content
  648 + }
  649 + }
  650 + }
  651 + return ""
  652 +}
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import "strings"
  13 +
  14 +// GetMergeCells provides a function to get all merged cells from a worksheet
  15 +// currently.
  16 +func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
  17 + var mergeCells []MergeCell
  18 + xlsx, err := f.workSheetReader(sheet)
  19 + if err != nil {
  20 + return mergeCells, err
  21 + }
  22 + if xlsx.MergeCells != nil {
  23 + mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
  24 +
  25 + for i := range xlsx.MergeCells.Cells {
  26 + ref := xlsx.MergeCells.Cells[i].Ref
  27 + axis := strings.Split(ref, ":")[0]
  28 + val, _ := f.GetCellValue(sheet, axis)
  29 + mergeCells = append(mergeCells, []string{ref, val})
  30 + }
  31 + }
  32 +
  33 + return mergeCells, err
  34 +}
  35 +
  36 +// MergeCell define a merged cell data.
  37 +// It consists of the following structure.
  38 +// example: []string{"D4:E10", "cell value"}
  39 +type MergeCell []string
  40 +
  41 +// GetCellValue returns merged cell value.
  42 +func (m *MergeCell) GetCellValue() string {
  43 + return (*m)[1]
  44 +}
  45 +
  46 +// GetStartAxis returns the merge start axis.
  47 +// example: "C2"
  48 +func (m *MergeCell) GetStartAxis() string {
  49 + axis := strings.Split((*m)[0], ":")
  50 + return axis[0]
  51 +}
  52 +
  53 +// GetEndAxis returns the merge end axis.
  54 +// example: "D4"
  55 +func (m *MergeCell) GetEndAxis() string {
  56 + axis := strings.Split((*m)[0], ":")
  57 + return axis[1]
  58 +}
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import (
  13 + "encoding/json"
  14 + "encoding/xml"
  15 + "errors"
  16 + "strconv"
  17 + "strings"
  18 +)
  19 +
  20 +// This section defines the currently supported chart types.
  21 +const (
  22 + Area = "area"
  23 + AreaStacked = "areaStacked"
  24 + AreaPercentStacked = "areaPercentStacked"
  25 + Area3D = "area3D"
  26 + Area3DStacked = "area3DStacked"
  27 + Area3DPercentStacked = "area3DPercentStacked"
  28 + Bar = "bar"
  29 + BarStacked = "barStacked"
  30 + BarPercentStacked = "barPercentStacked"
  31 + Bar3DClustered = "bar3DClustered"
  32 + Bar3DStacked = "bar3DStacked"
  33 + Bar3DPercentStacked = "bar3DPercentStacked"
  34 + Bar3DConeClustered = "bar3DConeClustered"
  35 + Bar3DConeStacked = "bar3DConeStacked"
  36 + Bar3DConePercentStacked = "bar3DConePercentStacked"
  37 + Bar3DPyramidClustered = "bar3DPyramidClustered"
  38 + Bar3DPyramidStacked = "bar3DPyramidStacked"
  39 + Bar3DPyramidPercentStacked = "bar3DPyramidPercentStacked"
  40 + Bar3DCylinderClustered = "bar3DCylinderClustered"
  41 + Bar3DCylinderStacked = "bar3DCylinderStacked"
  42 + Bar3DCylinderPercentStacked = "bar3DCylinderPercentStacked"
  43 + Col = "col"
  44 + ColStacked = "colStacked"
  45 + ColPercentStacked = "colPercentStacked"
  46 + Col3D = "col3D"
  47 + Col3DClustered = "col3DClustered"
  48 + Col3DStacked = "col3DStacked"
  49 + Col3DPercentStacked = "col3DPercentStacked"
  50 + Col3DCone = "col3DCone"
  51 + Col3DConeClustered = "col3DConeClustered"
  52 + Col3DConeStacked = "col3DConeStacked"
  53 + Col3DConePercentStacked = "col3DConePercentStacked"
  54 + Col3DPyramid = "col3DPyramid"
  55 + Col3DPyramidClustered = "col3DPyramidClustered"
  56 + Col3DPyramidStacked = "col3DPyramidStacked"
  57 + Col3DPyramidPercentStacked = "col3DPyramidPercentStacked"
  58 + Col3DCylinder = "col3DCylinder"
  59 + Col3DCylinderClustered = "col3DCylinderClustered"
  60 + Col3DCylinderStacked = "col3DCylinderStacked"
  61 + Col3DCylinderPercentStacked = "col3DCylinderPercentStacked"
  62 + Doughnut = "doughnut"
  63 + Line = "line"
  64 + Pie = "pie"
  65 + Pie3D = "pie3D"
  66 + Radar = "radar"
  67 + Scatter = "scatter"
  68 + Surface3D = "surface3D"
  69 + WireframeSurface3D = "wireframeSurface3D"
  70 + Contour = "contour"
  71 + WireframeContour = "wireframeContour"
  72 + Bubble = "bubble"
  73 + Bubble3D = "bubble3D"
  74 +)
  75 +
  76 +// This section defines the default value of chart properties.
  77 +var (
  78 + chartView3DRotX = map[string]int{
  79 + Area: 0,
  80 + AreaStacked: 0,
  81 + AreaPercentStacked: 0,
  82 + Area3D: 15,
  83 + Area3DStacked: 15,
  84 + Area3DPercentStacked: 15,
  85 + Bar: 0,
  86 + BarStacked: 0,
  87 + BarPercentStacked: 0,
  88 + Bar3DClustered: 15,
  89 + Bar3DStacked: 15,
  90 + Bar3DPercentStacked: 15,
  91 + Bar3DConeClustered: 15,
  92 + Bar3DConeStacked: 15,
  93 + Bar3DConePercentStacked: 15,
  94 + Bar3DPyramidClustered: 15,
  95 + Bar3DPyramidStacked: 15,
  96 + Bar3DPyramidPercentStacked: 15,
  97 + Bar3DCylinderClustered: 15,
  98 + Bar3DCylinderStacked: 15,
  99 + Bar3DCylinderPercentStacked: 15,
  100 + Col: 0,
  101 + ColStacked: 0,
  102 + ColPercentStacked: 0,
  103 + Col3D: 15,
  104 + Col3DClustered: 15,
  105 + Col3DStacked: 15,
  106 + Col3DPercentStacked: 15,
  107 + Col3DCone: 15,
  108 + Col3DConeClustered: 15,
  109 + Col3DConeStacked: 15,
  110 + Col3DConePercentStacked: 15,
  111 + Col3DPyramid: 15,
  112 + Col3DPyramidClustered: 15,
  113 + Col3DPyramidStacked: 15,
  114 + Col3DPyramidPercentStacked: 15,
  115 + Col3DCylinder: 15,
  116 + Col3DCylinderClustered: 15,
  117 + Col3DCylinderStacked: 15,
  118 + Col3DCylinderPercentStacked: 15,
  119 + Doughnut: 0,
  120 + Line: 0,
  121 + Pie: 0,
  122 + Pie3D: 30,
  123 + Radar: 0,
  124 + Scatter: 0,
  125 + Surface3D: 15,
  126 + WireframeSurface3D: 15,
  127 + Contour: 90,
  128 + WireframeContour: 90,
  129 + }
  130 + chartView3DRotY = map[string]int{
  131 + Area: 0,
  132 + AreaStacked: 0,
  133 + AreaPercentStacked: 0,
  134 + Area3D: 20,
  135 + Area3DStacked: 20,
  136 + Area3DPercentStacked: 20,
  137 + Bar: 0,
  138 + BarStacked: 0,
  139 + BarPercentStacked: 0,
  140 + Bar3DClustered: 20,
  141 + Bar3DStacked: 20,
  142 + Bar3DPercentStacked: 20,
  143 + Bar3DConeClustered: 20,
  144 + Bar3DConeStacked: 20,
  145 + Bar3DConePercentStacked: 20,
  146 + Bar3DPyramidClustered: 20,
  147 + Bar3DPyramidStacked: 20,
  148 + Bar3DPyramidPercentStacked: 20,
  149 + Bar3DCylinderClustered: 20,
  150 + Bar3DCylinderStacked: 20,
  151 + Bar3DCylinderPercentStacked: 20,
  152 + Col: 0,
  153 + ColStacked: 0,
  154 + ColPercentStacked: 0,
  155 + Col3D: 20,
  156 + Col3DClustered: 20,
  157 + Col3DStacked: 20,
  158 + Col3DPercentStacked: 20,
  159 + Col3DCone: 20,
  160 + Col3DConeClustered: 20,
  161 + Col3DConeStacked: 20,
  162 + Col3DConePercentStacked: 20,
  163 + Col3DPyramid: 20,
  164 + Col3DPyramidClustered: 20,
  165 + Col3DPyramidStacked: 20,
  166 + Col3DPyramidPercentStacked: 20,
  167 + Col3DCylinder: 20,
  168 + Col3DCylinderClustered: 20,
  169 + Col3DCylinderStacked: 20,
  170 + Col3DCylinderPercentStacked: 20,
  171 + Doughnut: 0,
  172 + Line: 0,
  173 + Pie: 0,
  174 + Pie3D: 0,
  175 + Radar: 0,
  176 + Scatter: 0,
  177 + Surface3D: 20,
  178 + WireframeSurface3D: 20,
  179 + Contour: 0,
  180 + WireframeContour: 0,
  181 + }
  182 + plotAreaChartOverlap = map[string]int{
  183 + BarStacked: 100,
  184 + BarPercentStacked: 100,
  185 + ColStacked: 100,
  186 + ColPercentStacked: 100,
  187 + }
  188 + chartView3DPerspective = map[string]int{
  189 + Contour: 0,
  190 + WireframeContour: 0,
  191 + }
  192 + chartView3DRAngAx = map[string]int{
  193 + Area: 0,
  194 + AreaStacked: 0,
  195 + AreaPercentStacked: 0,
  196 + Area3D: 1,
  197 + Area3DStacked: 1,
  198 + Area3DPercentStacked: 1,
  199 + Bar: 0,
  200 + BarStacked: 0,
  201 + BarPercentStacked: 0,
  202 + Bar3DClustered: 1,
  203 + Bar3DStacked: 1,
  204 + Bar3DPercentStacked: 1,
  205 + Bar3DConeClustered: 1,
  206 + Bar3DConeStacked: 1,
  207 + Bar3DConePercentStacked: 1,
  208 + Bar3DPyramidClustered: 1,
  209 + Bar3DPyramidStacked: 1,
  210 + Bar3DPyramidPercentStacked: 1,
  211 + Bar3DCylinderClustered: 1,
  212 + Bar3DCylinderStacked: 1,
  213 + Bar3DCylinderPercentStacked: 1,
  214 + Col: 0,
  215 + ColStacked: 0,
  216 + ColPercentStacked: 0,
  217 + Col3D: 1,
  218 + Col3DClustered: 1,
  219 + Col3DStacked: 1,
  220 + Col3DPercentStacked: 1,
  221 + Col3DCone: 1,
  222 + Col3DConeClustered: 1,
  223 + Col3DConeStacked: 1,
  224 + Col3DConePercentStacked: 1,
  225 + Col3DPyramid: 1,
  226 + Col3DPyramidClustered: 1,
  227 + Col3DPyramidStacked: 1,
  228 + Col3DPyramidPercentStacked: 1,
  229 + Col3DCylinder: 1,
  230 + Col3DCylinderClustered: 1,
  231 + Col3DCylinderStacked: 1,
  232 + Col3DCylinderPercentStacked: 1,
  233 + Doughnut: 0,
  234 + Line: 0,
  235 + Pie: 0,
  236 + Pie3D: 0,
  237 + Radar: 0,
  238 + Scatter: 0,
  239 + Surface3D: 0,
  240 + WireframeSurface3D: 0,
  241 + Contour: 0,
  242 + Bubble: 0,
  243 + Bubble3D: 0,
  244 + }
  245 + chartLegendPosition = map[string]string{
  246 + "bottom": "b",
  247 + "left": "l",
  248 + "right": "r",
  249 + "top": "t",
  250 + "top_right": "tr",
  251 + }
  252 + chartValAxNumFmtFormatCode = map[string]string{
  253 + Area: "General",
  254 + AreaStacked: "General",
  255 + AreaPercentStacked: "0%",
  256 + Area3D: "General",
  257 + Area3DStacked: "General",
  258 + Area3DPercentStacked: "0%",
  259 + Bar: "General",
  260 + BarStacked: "General",
  261 + BarPercentStacked: "0%",
  262 + Bar3DClustered: "General",
  263 + Bar3DStacked: "General",
  264 + Bar3DPercentStacked: "0%",
  265 + Bar3DConeClustered: "General",
  266 + Bar3DConeStacked: "General",
  267 + Bar3DConePercentStacked: "0%",
  268 + Bar3DPyramidClustered: "General",
  269 + Bar3DPyramidStacked: "General",
  270 + Bar3DPyramidPercentStacked: "0%",
  271 + Bar3DCylinderClustered: "General",
  272 + Bar3DCylinderStacked: "General",
  273 + Bar3DCylinderPercentStacked: "0%",
  274 + Col: "General",
  275 + ColStacked: "General",
  276 + ColPercentStacked: "0%",
  277 + Col3D: "General",
  278 + Col3DClustered: "General",
  279 + Col3DStacked: "General",
  280 + Col3DPercentStacked: "0%",
  281 + Col3DCone: "General",
  282 + Col3DConeClustered: "General",
  283 + Col3DConeStacked: "General",
  284 + Col3DConePercentStacked: "0%",
  285 + Col3DPyramid: "General",
  286 + Col3DPyramidClustered: "General",
  287 + Col3DPyramidStacked: "General",
  288 + Col3DPyramidPercentStacked: "0%",
  289 + Col3DCylinder: "General",
  290 + Col3DCylinderClustered: "General",
  291 + Col3DCylinderStacked: "General",
  292 + Col3DCylinderPercentStacked: "0%",
  293 + Doughnut: "General",
  294 + Line: "General",
  295 + Pie: "General",
  296 + Pie3D: "General",
  297 + Radar: "General",
  298 + Scatter: "General",
  299 + Surface3D: "General",
  300 + WireframeSurface3D: "General",
  301 + Contour: "General",
  302 + WireframeContour: "General",
  303 + Bubble: "General",
  304 + Bubble3D: "General",
  305 + }
  306 + chartValAxCrossBetween = map[string]string{
  307 + Area: "midCat",
  308 + AreaStacked: "midCat",
  309 + AreaPercentStacked: "midCat",
  310 + Area3D: "midCat",
  311 + Area3DStacked: "midCat",
  312 + Area3DPercentStacked: "midCat",
  313 + Bar: "between",
  314 + BarStacked: "between",
  315 + BarPercentStacked: "between",
  316 + Bar3DClustered: "between",
  317 + Bar3DStacked: "between",
  318 + Bar3DPercentStacked: "between",
  319 + Bar3DConeClustered: "between",
  320 + Bar3DConeStacked: "between",
  321 + Bar3DConePercentStacked: "between",
  322 + Bar3DPyramidClustered: "between",
  323 + Bar3DPyramidStacked: "between",
  324 + Bar3DPyramidPercentStacked: "between",
  325 + Bar3DCylinderClustered: "between",
  326 + Bar3DCylinderStacked: "between",
  327 + Bar3DCylinderPercentStacked: "between",
  328 + Col: "between",
  329 + ColStacked: "between",
  330 + ColPercentStacked: "between",
  331 + Col3D: "between",
  332 + Col3DClustered: "between",
  333 + Col3DStacked: "between",
  334 + Col3DPercentStacked: "between",
  335 + Col3DCone: "between",
  336 + Col3DConeClustered: "between",
  337 + Col3DConeStacked: "between",
  338 + Col3DConePercentStacked: "between",
  339 + Col3DPyramid: "between",
  340 + Col3DPyramidClustered: "between",
  341 + Col3DPyramidStacked: "between",
  342 + Col3DPyramidPercentStacked: "between",
  343 + Col3DCylinder: "between",
  344 + Col3DCylinderClustered: "between",
  345 + Col3DCylinderStacked: "between",
  346 + Col3DCylinderPercentStacked: "between",
  347 + Doughnut: "between",
  348 + Line: "between",
  349 + Pie: "between",
  350 + Pie3D: "between",
  351 + Radar: "between",
  352 + Scatter: "between",
  353 + Surface3D: "midCat",
  354 + WireframeSurface3D: "midCat",
  355 + Contour: "midCat",
  356 + WireframeContour: "midCat",
  357 + Bubble: "midCat",
  358 + Bubble3D: "midCat",
  359 + }
  360 + plotAreaChartGrouping = map[string]string{
  361 + Area: "standard",
  362 + AreaStacked: "stacked",
  363 + AreaPercentStacked: "percentStacked",
  364 + Area3D: "standard",
  365 + Area3DStacked: "stacked",
  366 + Area3DPercentStacked: "percentStacked",
  367 + Bar: "clustered",
  368 + BarStacked: "stacked",
  369 + BarPercentStacked: "percentStacked",
  370 + Bar3DClustered: "clustered",
  371 + Bar3DStacked: "stacked",
  372 + Bar3DPercentStacked: "percentStacked",
  373 + Bar3DConeClustered: "clustered",
  374 + Bar3DConeStacked: "stacked",
  375 + Bar3DConePercentStacked: "percentStacked",
  376 + Bar3DPyramidClustered: "clustered",
  377 + Bar3DPyramidStacked: "stacked",
  378 + Bar3DPyramidPercentStacked: "percentStacked",
  379 + Bar3DCylinderClustered: "clustered",
  380 + Bar3DCylinderStacked: "stacked",
  381 + Bar3DCylinderPercentStacked: "percentStacked",
  382 + Col: "clustered",
  383 + ColStacked: "stacked",
  384 + ColPercentStacked: "percentStacked",
  385 + Col3D: "standard",
  386 + Col3DClustered: "clustered",
  387 + Col3DStacked: "stacked",
  388 + Col3DPercentStacked: "percentStacked",
  389 + Col3DCone: "standard",
  390 + Col3DConeClustered: "clustered",
  391 + Col3DConeStacked: "stacked",
  392 + Col3DConePercentStacked: "percentStacked",
  393 + Col3DPyramid: "standard",
  394 + Col3DPyramidClustered: "clustered",
  395 + Col3DPyramidStacked: "stacked",
  396 + Col3DPyramidPercentStacked: "percentStacked",
  397 + Col3DCylinder: "standard",
  398 + Col3DCylinderClustered: "clustered",
  399 + Col3DCylinderStacked: "stacked",
  400 + Col3DCylinderPercentStacked: "percentStacked",
  401 + Line: "standard",
  402 + }
  403 + plotAreaChartBarDir = map[string]string{
  404 + Bar: "bar",
  405 + BarStacked: "bar",
  406 + BarPercentStacked: "bar",
  407 + Bar3DClustered: "bar",
  408 + Bar3DStacked: "bar",
  409 + Bar3DPercentStacked: "bar",
  410 + Bar3DConeClustered: "bar",
  411 + Bar3DConeStacked: "bar",
  412 + Bar3DConePercentStacked: "bar",
  413 + Bar3DPyramidClustered: "bar",
  414 + Bar3DPyramidStacked: "bar",
  415 + Bar3DPyramidPercentStacked: "bar",
  416 + Bar3DCylinderClustered: "bar",
  417 + Bar3DCylinderStacked: "bar",
  418 + Bar3DCylinderPercentStacked: "bar",
  419 + Col: "col",
  420 + ColStacked: "col",
  421 + ColPercentStacked: "col",
  422 + Col3D: "col",
  423 + Col3DClustered: "col",
  424 + Col3DStacked: "col",
  425 + Col3DPercentStacked: "col",
  426 + Col3DCone: "col",
  427 + Col3DConeStacked: "col",
  428 + Col3DConeClustered: "col",
  429 + Col3DConePercentStacked: "col",
  430 + Col3DPyramid: "col",
  431 + Col3DPyramidClustered: "col",
  432 + Col3DPyramidStacked: "col",
  433 + Col3DPyramidPercentStacked: "col",
  434 + Col3DCylinder: "col",
  435 + Col3DCylinderClustered: "col",
  436 + Col3DCylinderStacked: "col",
  437 + Col3DCylinderPercentStacked: "col",
  438 + Line: "standard",
  439 + }
  440 + orientation = map[bool]string{
  441 + true: "maxMin",
  442 + false: "minMax",
  443 + }
  444 + catAxPos = map[bool]string{
  445 + true: "t",
  446 + false: "b",
  447 + }
  448 + valAxPos = map[bool]string{
  449 + true: "r",
  450 + false: "l",
  451 + }
  452 + valTickLblPos = map[string]string{
  453 + Contour: "none",
  454 + WireframeContour: "none",
  455 + }
  456 +)
  457 +
  458 +// parseFormatChartSet provides a function to parse the format settings of the
  459 +// chart with default value.
  460 +func parseFormatChartSet(formatSet string) (*formatChart, error) {
  461 + format := formatChart{
  462 + Dimension: formatChartDimension{
  463 + Width: 480,
  464 + Height: 290,
  465 + },
  466 + Format: formatPicture{
  467 + FPrintsWithSheet: true,
  468 + FLocksWithSheet: false,
  469 + NoChangeAspect: false,
  470 + OffsetX: 0,
  471 + OffsetY: 0,
  472 + XScale: 1.0,
  473 + YScale: 1.0,
  474 + },
  475 + Legend: formatChartLegend{
  476 + Position: "bottom",
  477 + ShowLegendKey: false,
  478 + },
  479 + Title: formatChartTitle{
  480 + Name: " ",
  481 + },
  482 + ShowBlanksAs: "gap",
  483 + }
  484 + err := json.Unmarshal([]byte(formatSet), &format)
  485 + return &format, err
  486 +}
  487 +
  488 +// AddChart provides the method to add chart in a sheet by given chart format
  489 +// set (such as offset, scale, aspect ratio setting and print settings) and
  490 +// properties set. For example, create 3D clustered column chart with data
  491 +// Sheet1!$A$29:$D$32:
  492 +//
  493 +// package main
  494 +//
  495 +// import (
  496 +// "fmt"
  497 +//
  498 +// "github.com/360EntSecGroup-Skylar/excelize"
  499 +// )
  500 +//
  501 +// func main() {
  502 +// categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
  503 +// values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
  504 +// f := excelize.NewFile()
  505 +// for k, v := range categories {
  506 +// f.SetCellValue("Sheet1", k, v)
  507 +// }
  508 +// for k, v := range values {
  509 +// f.SetCellValue("Sheet1", k, v)
  510 +// }
  511 +// err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","dimension":{"width":640,"height":480},"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Fruit 3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"reverse_order":true},"y_axis":{"maximum":7.5,"minimum":0.5}}`)
  512 +// if err != nil {
  513 +// fmt.Println(err)
  514 +// return
  515 +// }
  516 +// // Save xlsx file by the given path.
  517 +// err = xlsx.SaveAs("./Book1.xlsx")
  518 +// if err != nil {
  519 +// fmt.Println(err)
  520 +// }
  521 +// }
  522 +//
  523 +// The following shows the type of chart supported by excelize:
  524 +//
  525 +// Type | Chart
  526 +// -----------------------------+------------------------------
  527 +// area | 2D area chart
  528 +// areaStacked | 2D stacked area chart
  529 +// areaPercentStacked | 2D 100% stacked area chart
  530 +// area3D | 3D area chart
  531 +// area3DStacked | 3D stacked area chart
  532 +// area3DPercentStacked | 3D 100% stacked area chart
  533 +// bar | 2D clustered bar chart
  534 +// barStacked | 2D stacked bar chart
  535 +// barPercentStacked | 2D 100% stacked bar chart
  536 +// bar3DClustered | 3D clustered bar chart
  537 +// bar3DStacked | 3D stacked bar chart
  538 +// bar3DPercentStacked | 3D 100% stacked bar chart
  539 +// bar3DConeClustered | 3D cone clustered bar chart
  540 +// bar3DConeStacked | 3D cone stacked bar chart
  541 +// bar3DConePercentStacked | 3D cone percent bar chart
  542 +// bar3DPyramidClustered | 3D pyramid clustered bar chart
  543 +// bar3DPyramidStacked | 3D pyramid stacked bar chart
  544 +// bar3DPyramidPercentStacked | 3D pyramid percent stacked bar chart
  545 +// bar3DCylinderClustered | 3D cylinder clustered bar chart
  546 +// bar3DCylinderStacked | 3D cylinder stacked bar chart
  547 +// bar3DCylinderPercentStacked | 3D cylinder percent stacked bar chart
  548 +// col | 2D clustered column chart
  549 +// colStacked | 2D stacked column chart
  550 +// colPercentStacked | 2D 100% stacked column chart
  551 +// col3DClustered | 3D clustered column chart
  552 +// col3D | 3D column chart
  553 +// col3DStacked | 3D stacked column chart
  554 +// col3DPercentStacked | 3D 100% stacked column chart
  555 +// col3DCone | 3D cone column chart
  556 +// col3DConeClustered | 3D cone clustered column chart
  557 +// col3DConeStacked | 3D cone stacked column chart
  558 +// col3DConePercentStacked | 3D cone percent stacked column chart
  559 +// col3DPyramid | 3D pyramid column chart
  560 +// col3DPyramidClustered | 3D pyramid clustered column chart
  561 +// col3DPyramidStacked | 3D pyramid stacked column chart
  562 +// col3DPyramidPercentStacked | 3D pyramid percent stacked column chart
  563 +// col3DCylinder | 3D cylinder column chart
  564 +// col3DCylinderClustered | 3D cylinder clustered column chart
  565 +// col3DCylinderStacked | 3D cylinder stacked column chart
  566 +// col3DCylinderPercentStacked | 3D cylinder percent stacked column chart
  567 +// doughnut | doughnut chart
  568 +// line | line chart
  569 +// pie | pie chart
  570 +// pie3D | 3D pie chart
  571 +// radar | radar chart
  572 +// scatter | scatter chart
  573 +// surface3D | 3D surface chart
  574 +// wireframeSurface3D | 3D wireframe surface chart
  575 +// contour | contour chart
  576 +// wireframeContour | wireframe contour chart
  577 +// bubble | bubble chart
  578 +// bubble3D | 3D bubble chart
  579 +//
  580 +// In Excel a chart series is a collection of information that defines which data is plotted such as values, axis labels and formatting.
  581 +//
  582 +// The series options that can be set are:
  583 +//
  584 +// name
  585 +// categories
  586 +// values
  587 +//
  588 +// name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The name property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1
  589 +//
  590 +// categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the categories property is optional and the chart will just assume a sequential series from 1..n.
  591 +//
  592 +// values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays.
  593 +//
  594 +// Set properties of the chart legend. The options that can be set are:
  595 +//
  596 +// position
  597 +// show_legend_key
  598 +//
  599 +// position: Set the position of the chart legend. The default legend position is right. The available positions are:
  600 +//
  601 +// top
  602 +// bottom
  603 +// left
  604 +// right
  605 +// top_right
  606 +//
  607 +// show_legend_key: Set the legend keys shall be shown in data labels. The default value is false.
  608 +//
  609 +// Set properties of the chart title. The properties that can be set are:
  610 +//
  611 +// title
  612 +//
  613 +// name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheetname. The name property is optional. The default is to have no chart title.
  614 +//
  615 +// Specifies how blank cells are plotted on the chart by show_blanks_as. The default value is gap. The options that can be set are:
  616 +//
  617 +// gap
  618 +// span
  619 +// zero
  620 +//
  621 +// gap: Specifies that blank values shall be left as a gap.
  622 +//
  623 +// sapn: Specifies that blank values shall be spanned with a line.
  624 +//
  625 +// zero: Specifies that blank values shall be treated as zero.
  626 +//
  627 +// Set chart offset, scale, aspect ratio setting and print settings by format, same as function AddPicture.
  628 +//
  629 +// Set the position of the chart plot area by plotarea. The properties that can be set are:
  630 +//
  631 +// show_bubble_size
  632 +// show_cat_name
  633 +// show_leader_lines
  634 +// show_percent
  635 +// show_series_name
  636 +// show_val
  637 +//
  638 +// show_bubble_size: Specifies the bubble size shall be shown in a data label. The show_bubble_size property is optional. The default value is false.
  639 +//
  640 +// show_cat_name: Specifies that the category name shall be shown in the data label. The show_cat_name property is optional. The default value is true.
  641 +//
  642 +// show_leader_lines: Specifies leader lines shall be shown for data labels. The show_leader_lines property is optional. The default value is false.
  643 +//
  644 +// show_percent: Specifies that the percentage shall be shown in a data label. The show_percent property is optional. The default value is false.
  645 +//
  646 +// show_series_name: Specifies that the series name shall be shown in a data label. The show_series_name property is optional. The default value is false.
  647 +//
  648 +// show_val: Specifies that the value shall be shown in a data label. The show_val property is optional. The default value is false.
  649 +//
  650 +// Set the primary horizontal and vertical axis options by x_axis and y_axis. The properties that can be set are:
  651 +//
  652 +// reverse_order
  653 +// maximum
  654 +// minimum
  655 +//
  656 +// reverse_order: Specifies that the categories or values on reverse order (orientation of the chart). The reverse_order property is optional. The default value is false.
  657 +//
  658 +// maximum: Specifies that the fixed maximum, 0 is auto. The maximum property is optional. The default value is auto.
  659 +//
  660 +// minimum: Specifies that the fixed minimum, 0 is auto. The minimum property is optional. The default value is auto.
  661 +//
  662 +// Set chart size by dimension property. The dimension property is optional. The default width is 480, and height is 290.
  663 +//
  664 +func (f *File) AddChart(sheet, cell, format string) error {
  665 + formatSet, err := parseFormatChartSet(format)
  666 + if err != nil {
  667 + return err
  668 + }
  669 + // Read sheet data.
  670 + xlsx, err := f.workSheetReader(sheet)
  671 + if err != nil {
  672 + return err
  673 + }
  674 + if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
  675 + return errors.New("unsupported chart type " + formatSet.Type)
  676 + }
  677 + // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
  678 + drawingID := f.countDrawings() + 1
  679 + chartID := f.countCharts() + 1
  680 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
  681 + drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML)
  682 + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
  683 + drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
  684 + err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format)
  685 + if err != nil {
  686 + return err
  687 + }
  688 + f.addChart(formatSet)
  689 + f.addContentTypePart(chartID, "chart")
  690 + f.addContentTypePart(drawingID, "drawings")
  691 + return err
  692 +}
  693 +
  694 +// countCharts provides a function to get chart files count storage in the
  695 +// folder xl/charts.
  696 +func (f *File) countCharts() int {
  697 + count := 0
  698 + for k := range f.XLSX {
  699 + if strings.Contains(k, "xl/charts/chart") {
  700 + count++
  701 + }
  702 + }
  703 + return count
  704 +}
  705 +
  706 +// prepareDrawing provides a function to prepare drawing ID and XML by given
  707 +// drawingID, worksheet name and default drawingXML.
  708 +func (f *File) prepareDrawing(xlsx *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
  709 + sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
  710 + if xlsx.Drawing != nil {
  711 + // The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
  712 + sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
  713 + drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
  714 + drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1)
  715 + } else {
  716 + // Add first picture for given sheet.
  717 + sheetPath, _ := f.sheetMap[trimSheetName(sheet)]
  718 + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
  719 + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
  720 + f.addSheetDrawing(sheet, rID)
  721 + }
  722 + return drawingID, drawingXML
  723 +}
  724 +
  725 +// addChart provides a function to create chart as xl/charts/chart%d.xml by
  726 +// given format sets.
  727 +func (f *File) addChart(formatSet *formatChart) {
  728 + count := f.countCharts()
  729 + xlsxChartSpace := xlsxChartSpace{
  730 + XMLNSc: NameSpaceDrawingMLChart,
  731 + XMLNSa: NameSpaceDrawingML,
  732 + XMLNSr: SourceRelationship,
  733 + XMLNSc16r2: SourceRelationshipChart201506,
  734 + Date1904: &attrValBool{Val: false},
  735 + Lang: &attrValString{Val: "en-US"},
  736 + RoundedCorners: &attrValBool{Val: false},
  737 + Chart: cChart{
  738 + Title: &cTitle{
  739 + Tx: cTx{
  740 + Rich: &cRich{
  741 + P: aP{
  742 + PPr: &aPPr{
  743 + DefRPr: aRPr{
  744 + Kern: 1200,
  745 + Strike: "noStrike",
  746 + U: "none",
  747 + Sz: 1400,
  748 + SolidFill: &aSolidFill{
  749 + SchemeClr: &aSchemeClr{
  750 + Val: "tx1",
  751 + LumMod: &attrValInt{
  752 + Val: 65000,
  753 + },
  754 + LumOff: &attrValInt{
  755 + Val: 35000,
  756 + },
  757 + },
  758 + },
  759 + Ea: &aEa{
  760 + Typeface: "+mn-ea",
  761 + },
  762 + Cs: &aCs{
  763 + Typeface: "+mn-cs",
  764 + },
  765 + Latin: &aLatin{
  766 + Typeface: "+mn-lt",
  767 + },
  768 + },
  769 + },
  770 + R: &aR{
  771 + RPr: aRPr{
  772 + Lang: "en-US",
  773 + AltLang: "en-US",
  774 + },
  775 + T: formatSet.Title.Name,
  776 + },
  777 + },
  778 + },
  779 + },
  780 + TxPr: cTxPr{
  781 + P: aP{
  782 + PPr: &aPPr{
  783 + DefRPr: aRPr{
  784 + Kern: 1200,
  785 + U: "none",
  786 + Sz: 14000,
  787 + Strike: "noStrike",
  788 + },
  789 + },
  790 + EndParaRPr: &aEndParaRPr{
  791 + Lang: "en-US",
  792 + },
  793 + },
  794 + },
  795 + },
  796 + View3D: &cView3D{
  797 + RotX: &attrValInt{Val: chartView3DRotX[formatSet.Type]},
  798 + RotY: &attrValInt{Val: chartView3DRotY[formatSet.Type]},
  799 + Perspective: &attrValInt{Val: chartView3DPerspective[formatSet.Type]},
  800 + RAngAx: &attrValInt{Val: chartView3DRAngAx[formatSet.Type]},
  801 + },
  802 + Floor: &cThicknessSpPr{
  803 + Thickness: &attrValInt{Val: 0},
  804 + },
  805 + SideWall: &cThicknessSpPr{
  806 + Thickness: &attrValInt{Val: 0},
  807 + },
  808 + BackWall: &cThicknessSpPr{
  809 + Thickness: &attrValInt{Val: 0},
  810 + },
  811 + PlotArea: &cPlotArea{},
  812 + Legend: &cLegend{
  813 + LegendPos: &attrValString{Val: chartLegendPosition[formatSet.Legend.Position]},
  814 + Overlay: &attrValBool{Val: false},
  815 + },
  816 +
  817 + PlotVisOnly: &attrValBool{Val: false},
  818 + DispBlanksAs: &attrValString{Val: formatSet.ShowBlanksAs},
  819 + ShowDLblsOverMax: &attrValBool{Val: false},
  820 + },
  821 + SpPr: &cSpPr{
  822 + SolidFill: &aSolidFill{
  823 + SchemeClr: &aSchemeClr{Val: "bg1"},
  824 + },
  825 + Ln: &aLn{
  826 + W: 9525,
  827 + Cap: "flat",
  828 + Cmpd: "sng",
  829 + Algn: "ctr",
  830 + SolidFill: &aSolidFill{
  831 + SchemeClr: &aSchemeClr{Val: "tx1",
  832 + LumMod: &attrValInt{
  833 + Val: 15000,
  834 + },
  835 + LumOff: &attrValInt{
  836 + Val: 85000,
  837 + },
  838 + },
  839 + },
  840 + },
  841 + },
  842 + PrintSettings: &cPrintSettings{
  843 + PageMargins: &cPageMargins{
  844 + B: 0.75,
  845 + L: 0.7,
  846 + R: 0.7,
  847 + T: 0.7,
  848 + Header: 0.3,
  849 + Footer: 0.3,
  850 + },
  851 + },
  852 + }
  853 + plotAreaFunc := map[string]func(*formatChart) *cPlotArea{
  854 + Area: f.drawBaseChart,
  855 + AreaStacked: f.drawBaseChart,
  856 + AreaPercentStacked: f.drawBaseChart,
  857 + Area3D: f.drawBaseChart,
  858 + Area3DStacked: f.drawBaseChart,
  859 + Area3DPercentStacked: f.drawBaseChart,
  860 + Bar: f.drawBaseChart,
  861 + BarStacked: f.drawBaseChart,
  862 + BarPercentStacked: f.drawBaseChart,
  863 + Bar3DClustered: f.drawBaseChart,
  864 + Bar3DStacked: f.drawBaseChart,
  865 + Bar3DPercentStacked: f.drawBaseChart,
  866 + Bar3DConeClustered: f.drawBaseChart,
  867 + Bar3DConeStacked: f.drawBaseChart,
  868 + Bar3DConePercentStacked: f.drawBaseChart,
  869 + Bar3DPyramidClustered: f.drawBaseChart,
  870 + Bar3DPyramidStacked: f.drawBaseChart,
  871 + Bar3DPyramidPercentStacked: f.drawBaseChart,
  872 + Bar3DCylinderClustered: f.drawBaseChart,
  873 + Bar3DCylinderStacked: f.drawBaseChart,
  874 + Bar3DCylinderPercentStacked: f.drawBaseChart,
  875 + Col: f.drawBaseChart,
  876 + ColStacked: f.drawBaseChart,
  877 + ColPercentStacked: f.drawBaseChart,
  878 + Col3D: f.drawBaseChart,
  879 + Col3DClustered: f.drawBaseChart,
  880 + Col3DStacked: f.drawBaseChart,
  881 + Col3DPercentStacked: f.drawBaseChart,
  882 + Col3DCone: f.drawBaseChart,
  883 + Col3DConeClustered: f.drawBaseChart,
  884 + Col3DConeStacked: f.drawBaseChart,
  885 + Col3DConePercentStacked: f.drawBaseChart,
  886 + Col3DPyramid: f.drawBaseChart,
  887 + Col3DPyramidClustered: f.drawBaseChart,
  888 + Col3DPyramidStacked: f.drawBaseChart,
  889 + Col3DPyramidPercentStacked: f.drawBaseChart,
  890 + Col3DCylinder: f.drawBaseChart,
  891 + Col3DCylinderClustered: f.drawBaseChart,
  892 + Col3DCylinderStacked: f.drawBaseChart,
  893 + Col3DCylinderPercentStacked: f.drawBaseChart,
  894 + Doughnut: f.drawDoughnutChart,
  895 + Line: f.drawLineChart,
  896 + Pie3D: f.drawPie3DChart,
  897 + Pie: f.drawPieChart,
  898 + Radar: f.drawRadarChart,
  899 + Scatter: f.drawScatterChart,
  900 + Surface3D: f.drawSurface3DChart,
  901 + WireframeSurface3D: f.drawSurface3DChart,
  902 + Contour: f.drawSurfaceChart,
  903 + WireframeContour: f.drawSurfaceChart,
  904 + Bubble: f.drawBaseChart,
  905 + Bubble3D: f.drawBaseChart,
  906 + }
  907 + xlsxChartSpace.Chart.PlotArea = plotAreaFunc[formatSet.Type](formatSet)
  908 +
  909 + chart, _ := xml.Marshal(xlsxChartSpace)
  910 + media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml"
  911 + f.saveFileList(media, chart)
  912 +}
  913 +
  914 +// drawBaseChart provides a function to draw the c:plotArea element for bar,
  915 +// and column series charts by given format sets.
  916 +func (f *File) drawBaseChart(formatSet *formatChart) *cPlotArea {
  917 + c := cCharts{
  918 + BarDir: &attrValString{
  919 + Val: "col",
  920 + },
  921 + Grouping: &attrValString{
  922 + Val: "clustered",
  923 + },
  924 + VaryColors: &attrValBool{
  925 + Val: true,
  926 + },
  927 + Ser: f.drawChartSeries(formatSet),
  928 + Shape: f.drawChartShape(formatSet),
  929 + DLbls: f.drawChartDLbls(formatSet),
  930 + AxID: []*attrValInt{
  931 + {Val: 754001152},
  932 + {Val: 753999904},
  933 + },
  934 + Overlap: &attrValInt{Val: 100},
  935 + }
  936 + var ok bool
  937 + if c.BarDir.Val, ok = plotAreaChartBarDir[formatSet.Type]; !ok {
  938 + c.BarDir = nil
  939 + }
  940 + if c.Grouping.Val, ok = plotAreaChartGrouping[formatSet.Type]; !ok {
  941 + c.Grouping = nil
  942 + }
  943 + if c.Overlap.Val, ok = plotAreaChartOverlap[formatSet.Type]; !ok {
  944 + c.Overlap = nil
  945 + }
  946 + catAx := f.drawPlotAreaCatAx(formatSet)
  947 + valAx := f.drawPlotAreaValAx(formatSet)
  948 + charts := map[string]*cPlotArea{
  949 + "area": {
  950 + AreaChart: &c,
  951 + CatAx: catAx,
  952 + ValAx: valAx,
  953 + },
  954 + "areaStacked": {
  955 + AreaChart: &c,
  956 + CatAx: catAx,
  957 + ValAx: valAx,
  958 + },
  959 + "areaPercentStacked": {
  960 + AreaChart: &c,
  961 + CatAx: catAx,
  962 + ValAx: valAx,
  963 + },
  964 + "area3D": {
  965 + Area3DChart: &c,
  966 + CatAx: catAx,
  967 + ValAx: valAx,
  968 + },
  969 + "area3DStacked": {
  970 + Area3DChart: &c,
  971 + CatAx: catAx,
  972 + ValAx: valAx,
  973 + },
  974 + "area3DPercentStacked": {
  975 + Area3DChart: &c,
  976 + CatAx: catAx,
  977 + ValAx: valAx,
  978 + },
  979 + "bar": {
  980 + BarChart: &c,
  981 + CatAx: catAx,
  982 + ValAx: valAx,
  983 + },
  984 + "barStacked": {
  985 + BarChart: &c,
  986 + CatAx: catAx,
  987 + ValAx: valAx,
  988 + },
  989 + "barPercentStacked": {
  990 + BarChart: &c,
  991 + CatAx: catAx,
  992 + ValAx: valAx,
  993 + },
  994 + "bar3DClustered": {
  995 + Bar3DChart: &c,
  996 + CatAx: catAx,
  997 + ValAx: valAx,
  998 + },
  999 + "bar3DStacked": {
  1000 + Bar3DChart: &c,
  1001 + CatAx: catAx,
  1002 + ValAx: valAx,
  1003 + },
  1004 + "bar3DPercentStacked": {
  1005 + Bar3DChart: &c,
  1006 + CatAx: catAx,
  1007 + ValAx: valAx,
  1008 + },
  1009 + "bar3DConeClustered": {
  1010 + Bar3DChart: &c,
  1011 + CatAx: catAx,
  1012 + ValAx: valAx,
  1013 + },
  1014 + "bar3DConeStacked": {
  1015 + Bar3DChart: &c,
  1016 + CatAx: catAx,
  1017 + ValAx: valAx,
  1018 + },
  1019 + "bar3DConePercentStacked": {
  1020 + Bar3DChart: &c,
  1021 + CatAx: catAx,
  1022 + ValAx: valAx,
  1023 + },
  1024 + "bar3DPyramidClustered": {
  1025 + Bar3DChart: &c,
  1026 + CatAx: catAx,
  1027 + ValAx: valAx,
  1028 + },
  1029 + "bar3DPyramidStacked": {
  1030 + Bar3DChart: &c,
  1031 + CatAx: catAx,
  1032 + ValAx: valAx,
  1033 + },
  1034 + "bar3DPyramidPercentStacked": {
  1035 + Bar3DChart: &c,
  1036 + CatAx: catAx,
  1037 + ValAx: valAx,
  1038 + },
  1039 + "bar3DCylinderClustered": {
  1040 + Bar3DChart: &c,
  1041 + CatAx: catAx,
  1042 + ValAx: valAx,
  1043 + },
  1044 + "bar3DCylinderStacked": {
  1045 + Bar3DChart: &c,
  1046 + CatAx: catAx,
  1047 + ValAx: valAx,
  1048 + },
  1049 + "bar3DCylinderPercentStacked": {
  1050 + Bar3DChart: &c,
  1051 + CatAx: catAx,
  1052 + ValAx: valAx,
  1053 + },
  1054 + "col": {
  1055 + BarChart: &c,
  1056 + CatAx: catAx,
  1057 + ValAx: valAx,
  1058 + },
  1059 + "colStacked": {
  1060 + BarChart: &c,
  1061 + CatAx: catAx,
  1062 + ValAx: valAx,
  1063 + },
  1064 + "colPercentStacked": {
  1065 + BarChart: &c,
  1066 + CatAx: catAx,
  1067 + ValAx: valAx,
  1068 + },
  1069 + "col3D": {
  1070 + Bar3DChart: &c,
  1071 + CatAx: catAx,
  1072 + ValAx: valAx,
  1073 + },
  1074 + "col3DClustered": {
  1075 + Bar3DChart: &c,
  1076 + CatAx: catAx,
  1077 + ValAx: valAx,
  1078 + },
  1079 + "col3DStacked": {
  1080 + Bar3DChart: &c,
  1081 + CatAx: catAx,
  1082 + ValAx: valAx,
  1083 + },
  1084 + "col3DPercentStacked": {
  1085 + Bar3DChart: &c,
  1086 + CatAx: catAx,
  1087 + ValAx: valAx,
  1088 + },
  1089 + "col3DCone": {
  1090 + Bar3DChart: &c,
  1091 + CatAx: catAx,
  1092 + ValAx: valAx,
  1093 + },
  1094 + "col3DConeClustered": {
  1095 + Bar3DChart: &c,
  1096 + CatAx: catAx,
  1097 + ValAx: valAx,
  1098 + },
  1099 + "col3DConeStacked": {
  1100 + Bar3DChart: &c,
  1101 + CatAx: catAx,
  1102 + ValAx: valAx,
  1103 + },
  1104 + "col3DConePercentStacked": {
  1105 + Bar3DChart: &c,
  1106 + CatAx: catAx,
  1107 + ValAx: valAx,
  1108 + },
  1109 + "col3DPyramid": {
  1110 + Bar3DChart: &c,
  1111 + CatAx: catAx,
  1112 + ValAx: valAx,
  1113 + },
  1114 + "col3DPyramidClustered": {
  1115 + Bar3DChart: &c,
  1116 + CatAx: catAx,
  1117 + ValAx: valAx,
  1118 + },
  1119 + "col3DPyramidStacked": {
  1120 + Bar3DChart: &c,
  1121 + CatAx: catAx,
  1122 + ValAx: valAx,
  1123 + },
  1124 + "col3DPyramidPercentStacked": {
  1125 + Bar3DChart: &c,
  1126 + CatAx: catAx,
  1127 + ValAx: valAx,
  1128 + },
  1129 + "col3DCylinder": {
  1130 + Bar3DChart: &c,
  1131 + CatAx: catAx,
  1132 + ValAx: valAx,
  1133 + },
  1134 + "col3DCylinderClustered": {
  1135 + Bar3DChart: &c,
  1136 + CatAx: catAx,
  1137 + ValAx: valAx,
  1138 + },
  1139 + "col3DCylinderStacked": {
  1140 + Bar3DChart: &c,
  1141 + CatAx: catAx,
  1142 + ValAx: valAx,
  1143 + },
  1144 + "col3DCylinderPercentStacked": {
  1145 + Bar3DChart: &c,
  1146 + CatAx: catAx,
  1147 + ValAx: valAx,
  1148 + },
  1149 + "bubble": {
  1150 + BubbleChart: &c,
  1151 + CatAx: catAx,
  1152 + ValAx: valAx,
  1153 + },
  1154 + "bubble3D": {
  1155 + BubbleChart: &c,
  1156 + CatAx: catAx,
  1157 + ValAx: valAx,
  1158 + },
  1159 + }
  1160 + return charts[formatSet.Type]
  1161 +}
  1162 +
  1163 +// drawDoughnutChart provides a function to draw the c:plotArea element for
  1164 +// doughnut chart by given format sets.
  1165 +func (f *File) drawDoughnutChart(formatSet *formatChart) *cPlotArea {
  1166 + return &cPlotArea{
  1167 + DoughnutChart: &cCharts{
  1168 + VaryColors: &attrValBool{
  1169 + Val: true,
  1170 + },
  1171 + Ser: f.drawChartSeries(formatSet),
  1172 + HoleSize: &attrValInt{Val: 75},
  1173 + },
  1174 + }
  1175 +}
  1176 +
  1177 +// drawLineChart provides a function to draw the c:plotArea element for line
  1178 +// chart by given format sets.
  1179 +func (f *File) drawLineChart(formatSet *formatChart) *cPlotArea {
  1180 + return &cPlotArea{
  1181 + LineChart: &cCharts{
  1182 + Grouping: &attrValString{
  1183 + Val: plotAreaChartGrouping[formatSet.Type],
  1184 + },
  1185 + VaryColors: &attrValBool{
  1186 + Val: false,
  1187 + },
  1188 + Ser: f.drawChartSeries(formatSet),
  1189 + DLbls: f.drawChartDLbls(formatSet),
  1190 + Smooth: &attrValBool{
  1191 + Val: false,
  1192 + },
  1193 + AxID: []*attrValInt{
  1194 + {Val: 754001152},
  1195 + {Val: 753999904},
  1196 + },
  1197 + },
  1198 + CatAx: f.drawPlotAreaCatAx(formatSet),
  1199 + ValAx: f.drawPlotAreaValAx(formatSet),
  1200 + }
  1201 +}
  1202 +
  1203 +// drawPieChart provides a function to draw the c:plotArea element for pie
  1204 +// chart by given format sets.
  1205 +func (f *File) drawPieChart(formatSet *formatChart) *cPlotArea {
  1206 + return &cPlotArea{
  1207 + PieChart: &cCharts{
  1208 + VaryColors: &attrValBool{
  1209 + Val: true,
  1210 + },
  1211 + Ser: f.drawChartSeries(formatSet),
  1212 + },
  1213 + }
  1214 +}
  1215 +
  1216 +// drawPie3DChart provides a function to draw the c:plotArea element for 3D
  1217 +// pie chart by given format sets.
  1218 +func (f *File) drawPie3DChart(formatSet *formatChart) *cPlotArea {
  1219 + return &cPlotArea{
  1220 + Pie3DChart: &cCharts{
  1221 + VaryColors: &attrValBool{
  1222 + Val: true,
  1223 + },
  1224 + Ser: f.drawChartSeries(formatSet),
  1225 + },
  1226 + }
  1227 +}
  1228 +
  1229 +// drawRadarChart provides a function to draw the c:plotArea element for radar
  1230 +// chart by given format sets.
  1231 +func (f *File) drawRadarChart(formatSet *formatChart) *cPlotArea {
  1232 + return &cPlotArea{
  1233 + RadarChart: &cCharts{
  1234 + RadarStyle: &attrValString{
  1235 + Val: "marker",
  1236 + },
  1237 + VaryColors: &attrValBool{
  1238 + Val: false,
  1239 + },
  1240 + Ser: f.drawChartSeries(formatSet),
  1241 + DLbls: f.drawChartDLbls(formatSet),
  1242 + AxID: []*attrValInt{
  1243 + {Val: 754001152},
  1244 + {Val: 753999904},
  1245 + },
  1246 + },
  1247 + CatAx: f.drawPlotAreaCatAx(formatSet),
  1248 + ValAx: f.drawPlotAreaValAx(formatSet),
  1249 + }
  1250 +}
  1251 +
  1252 +// drawScatterChart provides a function to draw the c:plotArea element for
  1253 +// scatter chart by given format sets.
  1254 +func (f *File) drawScatterChart(formatSet *formatChart) *cPlotArea {
  1255 + return &cPlotArea{
  1256 + ScatterChart: &cCharts{
  1257 + ScatterStyle: &attrValString{
  1258 + Val: "smoothMarker", // line,lineMarker,marker,none,smooth,smoothMarker
  1259 + },
  1260 + VaryColors: &attrValBool{
  1261 + Val: false,
  1262 + },
  1263 + Ser: f.drawChartSeries(formatSet),
  1264 + DLbls: f.drawChartDLbls(formatSet),
  1265 + AxID: []*attrValInt{
  1266 + {Val: 754001152},
  1267 + {Val: 753999904},
  1268 + },
  1269 + },
  1270 + CatAx: f.drawPlotAreaCatAx(formatSet),
  1271 + ValAx: f.drawPlotAreaValAx(formatSet),
  1272 + }
  1273 +}
  1274 +
  1275 +// drawSurface3DChart provides a function to draw the c:surface3DChart element by
  1276 +// given format sets.
  1277 +func (f *File) drawSurface3DChart(formatSet *formatChart) *cPlotArea {
  1278 + plotArea := &cPlotArea{
  1279 + Surface3DChart: &cCharts{
  1280 + Ser: f.drawChartSeries(formatSet),
  1281 + AxID: []*attrValInt{
  1282 + {Val: 754001152},
  1283 + {Val: 753999904},
  1284 + {Val: 832256642},
  1285 + },
  1286 + },
  1287 + CatAx: f.drawPlotAreaCatAx(formatSet),
  1288 + ValAx: f.drawPlotAreaValAx(formatSet),
  1289 + SerAx: f.drawPlotAreaSerAx(formatSet),
  1290 + }
  1291 + if formatSet.Type == WireframeSurface3D {
  1292 + plotArea.Surface3DChart.Wireframe = &attrValBool{Val: true}
  1293 + }
  1294 + return plotArea
  1295 +}
  1296 +
  1297 +// drawSurfaceChart provides a function to draw the c:surfaceChart element by
  1298 +// given format sets.
  1299 +func (f *File) drawSurfaceChart(formatSet *formatChart) *cPlotArea {
  1300 + plotArea := &cPlotArea{
  1301 + SurfaceChart: &cCharts{
  1302 + Ser: f.drawChartSeries(formatSet),
  1303 + AxID: []*attrValInt{
  1304 + {Val: 754001152},
  1305 + {Val: 753999904},
  1306 + {Val: 832256642},
  1307 + },
  1308 + },
  1309 + CatAx: f.drawPlotAreaCatAx(formatSet),
  1310 + ValAx: f.drawPlotAreaValAx(formatSet),
  1311 + SerAx: f.drawPlotAreaSerAx(formatSet),
  1312 + }
  1313 + if formatSet.Type == WireframeContour {
  1314 + plotArea.SurfaceChart.Wireframe = &attrValBool{Val: true}
  1315 + }
  1316 + return plotArea
  1317 +}
  1318 +
  1319 +// drawChartShape provides a function to draw the c:shape element by given
  1320 +// format sets.
  1321 +func (f *File) drawChartShape(formatSet *formatChart) *attrValString {
  1322 + shapes := map[string]string{
  1323 + Bar3DConeClustered: "cone",
  1324 + Bar3DConeStacked: "cone",
  1325 + Bar3DConePercentStacked: "cone",
  1326 + Bar3DPyramidClustered: "pyramid",
  1327 + Bar3DPyramidStacked: "pyramid",
  1328 + Bar3DPyramidPercentStacked: "pyramid",
  1329 + Bar3DCylinderClustered: "cylinder",
  1330 + Bar3DCylinderStacked: "cylinder",
  1331 + Bar3DCylinderPercentStacked: "cylinder",
  1332 + Col3DCone: "cone",
  1333 + Col3DConeClustered: "cone",
  1334 + Col3DConeStacked: "cone",
  1335 + Col3DConePercentStacked: "cone",
  1336 + Col3DPyramid: "pyramid",
  1337 + Col3DPyramidClustered: "pyramid",
  1338 + Col3DPyramidStacked: "pyramid",
  1339 + Col3DPyramidPercentStacked: "pyramid",
  1340 + Col3DCylinder: "cylinder",
  1341 + Col3DCylinderClustered: "cylinder",
  1342 + Col3DCylinderStacked: "cylinder",
  1343 + Col3DCylinderPercentStacked: "cylinder",
  1344 + }
  1345 + if shape, ok := shapes[formatSet.Type]; ok {
  1346 + return &attrValString{Val: shape}
  1347 + }
  1348 + return nil
  1349 +}
  1350 +
  1351 +// drawChartSeries provides a function to draw the c:ser element by given
  1352 +// format sets.
  1353 +func (f *File) drawChartSeries(formatSet *formatChart) *[]cSer {
  1354 + ser := []cSer{}
  1355 + for k := range formatSet.Series {
  1356 + ser = append(ser, cSer{
  1357 + IDx: &attrValInt{Val: k},
  1358 + Order: &attrValInt{Val: k},
  1359 + Tx: &cTx{
  1360 + StrRef: &cStrRef{
  1361 + F: formatSet.Series[k].Name,
  1362 + },
  1363 + },
  1364 + SpPr: f.drawChartSeriesSpPr(k, formatSet),
  1365 + Marker: f.drawChartSeriesMarker(k, formatSet),
  1366 + DPt: f.drawChartSeriesDPt(k, formatSet),
  1367 + DLbls: f.drawChartSeriesDLbls(formatSet),
  1368 + Cat: f.drawChartSeriesCat(formatSet.Series[k], formatSet),
  1369 + Val: f.drawChartSeriesVal(formatSet.Series[k], formatSet),
  1370 + XVal: f.drawChartSeriesXVal(formatSet.Series[k], formatSet),
  1371 + YVal: f.drawChartSeriesYVal(formatSet.Series[k], formatSet),
  1372 + BubbleSize: f.drawCharSeriesBubbleSize(formatSet.Series[k], formatSet),
  1373 + Bubble3D: f.drawCharSeriesBubble3D(formatSet),
  1374 + })
  1375 + }
  1376 + return &ser
  1377 +}
  1378 +
  1379 +// drawChartSeriesSpPr provides a function to draw the c:spPr element by given
  1380 +// format sets.
  1381 +func (f *File) drawChartSeriesSpPr(i int, formatSet *formatChart) *cSpPr {
  1382 + spPrScatter := &cSpPr{
  1383 + Ln: &aLn{
  1384 + W: 25400,
  1385 + NoFill: " ",
  1386 + },
  1387 + }
  1388 + spPrLine := &cSpPr{
  1389 + Ln: &aLn{
  1390 + W: 25400,
  1391 + Cap: "rnd", // rnd, sq, flat
  1392 + },
  1393 + }
  1394 + if i < 6 {
  1395 + spPrLine.Ln.SolidFill = &aSolidFill{
  1396 + SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)},
  1397 + }
  1398 + }
  1399 + chartSeriesSpPr := map[string]*cSpPr{Line: spPrLine, Scatter: spPrScatter}
  1400 + return chartSeriesSpPr[formatSet.Type]
  1401 +}
  1402 +
  1403 +// drawChartSeriesDPt provides a function to draw the c:dPt element by given
  1404 +// data index and format sets.
  1405 +func (f *File) drawChartSeriesDPt(i int, formatSet *formatChart) []*cDPt {
  1406 + dpt := []*cDPt{{
  1407 + IDx: &attrValInt{Val: i},
  1408 + Bubble3D: &attrValBool{Val: false},
  1409 + SpPr: &cSpPr{
  1410 + SolidFill: &aSolidFill{
  1411 + SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)},
  1412 + },
  1413 + Ln: &aLn{
  1414 + W: 25400,
  1415 + Cap: "rnd",
  1416 + SolidFill: &aSolidFill{
  1417 + SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)},
  1418 + },
  1419 + },
  1420 + Sp3D: &aSp3D{
  1421 + ContourW: 25400,
  1422 + ContourClr: &aContourClr{
  1423 + SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)},
  1424 + },
  1425 + },
  1426 + },
  1427 + }}
  1428 + chartSeriesDPt := map[string][]*cDPt{Pie: dpt, Pie3D: dpt}
  1429 + return chartSeriesDPt[formatSet.Type]
  1430 +}
  1431 +
  1432 +// drawChartSeriesCat provides a function to draw the c:cat element by given
  1433 +// chart series and format sets.
  1434 +func (f *File) drawChartSeriesCat(v formatChartSeries, formatSet *formatChart) *cCat {
  1435 + cat := &cCat{
  1436 + StrRef: &cStrRef{
  1437 + F: v.Categories,
  1438 + },
  1439 + }
  1440 + chartSeriesCat := map[string]*cCat{Scatter: nil, Bubble: nil, Bubble3D: nil}
  1441 + if _, ok := chartSeriesCat[formatSet.Type]; ok {
  1442 + return nil
  1443 + }
  1444 + return cat
  1445 +}
  1446 +
  1447 +// drawChartSeriesVal provides a function to draw the c:val element by given
  1448 +// chart series and format sets.
  1449 +func (f *File) drawChartSeriesVal(v formatChartSeries, formatSet *formatChart) *cVal {
  1450 + val := &cVal{
  1451 + NumRef: &cNumRef{
  1452 + F: v.Values,
  1453 + },
  1454 + }
  1455 + chartSeriesVal := map[string]*cVal{Scatter: nil, Bubble: nil, Bubble3D: nil}
  1456 + if _, ok := chartSeriesVal[formatSet.Type]; ok {
  1457 + return nil
  1458 + }
  1459 + return val
  1460 +}
  1461 +
  1462 +// drawChartSeriesMarker provides a function to draw the c:marker element by
  1463 +// given data index and format sets.
  1464 +func (f *File) drawChartSeriesMarker(i int, formatSet *formatChart) *cMarker {
  1465 + marker := &cMarker{
  1466 + Symbol: &attrValString{Val: "circle"},
  1467 + Size: &attrValInt{Val: 5},
  1468 + }
  1469 + if i < 6 {
  1470 + marker.SpPr = &cSpPr{
  1471 + SolidFill: &aSolidFill{
  1472 + SchemeClr: &aSchemeClr{
  1473 + Val: "accent" + strconv.Itoa(i+1),
  1474 + },
  1475 + },
  1476 + Ln: &aLn{
  1477 + W: 9252,
  1478 + SolidFill: &aSolidFill{
  1479 + SchemeClr: &aSchemeClr{
  1480 + Val: "accent" + strconv.Itoa(i+1),
  1481 + },
  1482 + },
  1483 + },
  1484 + }
  1485 + }
  1486 + chartSeriesMarker := map[string]*cMarker{Scatter: marker}
  1487 + return chartSeriesMarker[formatSet.Type]
  1488 +}
  1489 +
  1490 +// drawChartSeriesXVal provides a function to draw the c:xVal element by given
  1491 +// chart series and format sets.
  1492 +func (f *File) drawChartSeriesXVal(v formatChartSeries, formatSet *formatChart) *cCat {
  1493 + cat := &cCat{
  1494 + StrRef: &cStrRef{
  1495 + F: v.Categories,
  1496 + },
  1497 + }
  1498 + chartSeriesXVal := map[string]*cCat{Scatter: cat}
  1499 + return chartSeriesXVal[formatSet.Type]
  1500 +}
  1501 +
  1502 +// drawChartSeriesYVal provides a function to draw the c:yVal element by given
  1503 +// chart series and format sets.
  1504 +func (f *File) drawChartSeriesYVal(v formatChartSeries, formatSet *formatChart) *cVal {
  1505 + val := &cVal{
  1506 + NumRef: &cNumRef{
  1507 + F: v.Values,
  1508 + },
  1509 + }
  1510 + chartSeriesYVal := map[string]*cVal{Scatter: val, Bubble: val, Bubble3D: val}
  1511 + return chartSeriesYVal[formatSet.Type]
  1512 +}
  1513 +
  1514 +// drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize
  1515 +// element by given chart series and format sets.
  1516 +func (f *File) drawCharSeriesBubbleSize(v formatChartSeries, formatSet *formatChart) *cVal {
  1517 + if _, ok := map[string]bool{Bubble: true, Bubble3D: true}[formatSet.Type]; !ok {
  1518 + return nil
  1519 + }
  1520 + return &cVal{
  1521 + NumRef: &cNumRef{
  1522 + F: v.Values,
  1523 + },
  1524 + }
  1525 +}
  1526 +
  1527 +// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element
  1528 +// by given format sets.
  1529 +func (f *File) drawCharSeriesBubble3D(formatSet *formatChart) *attrValBool {
  1530 + if _, ok := map[string]bool{Bubble3D: true}[formatSet.Type]; !ok {
  1531 + return nil
  1532 + }
  1533 + return &attrValBool{Val: true}
  1534 +}
  1535 +
  1536 +// drawChartDLbls provides a function to draw the c:dLbls element by given
  1537 +// format sets.
  1538 +func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls {
  1539 + return &cDLbls{
  1540 + ShowLegendKey: &attrValBool{Val: formatSet.Legend.ShowLegendKey},
  1541 + ShowVal: &attrValBool{Val: formatSet.Plotarea.ShowVal},
  1542 + ShowCatName: &attrValBool{Val: formatSet.Plotarea.ShowCatName},
  1543 + ShowSerName: &attrValBool{Val: formatSet.Plotarea.ShowSerName},
  1544 + ShowBubbleSize: &attrValBool{Val: formatSet.Plotarea.ShowBubbleSize},
  1545 + ShowPercent: &attrValBool{Val: formatSet.Plotarea.ShowPercent},
  1546 + ShowLeaderLines: &attrValBool{Val: formatSet.Plotarea.ShowLeaderLines},
  1547 + }
  1548 +}
  1549 +
  1550 +// drawChartSeriesDLbls provides a function to draw the c:dLbls element by
  1551 +// given format sets.
  1552 +func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls {
  1553 + dLbls := f.drawChartDLbls(formatSet)
  1554 + chartSeriesDLbls := map[string]*cDLbls{Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil}
  1555 + if _, ok := chartSeriesDLbls[formatSet.Type]; ok {
  1556 + return nil
  1557 + }
  1558 + return dLbls
  1559 +}
  1560 +
  1561 +// drawPlotAreaCatAx provides a function to draw the c:catAx element.
  1562 +func (f *File) drawPlotAreaCatAx(formatSet *formatChart) []*cAxs {
  1563 + min := &attrValFloat{Val: formatSet.XAxis.Minimum}
  1564 + max := &attrValFloat{Val: formatSet.XAxis.Maximum}
  1565 + if formatSet.XAxis.Minimum == 0 {
  1566 + min = nil
  1567 + }
  1568 + if formatSet.XAxis.Maximum == 0 {
  1569 + max = nil
  1570 + }
  1571 + axs := []*cAxs{
  1572 + {
  1573 + AxID: &attrValInt{Val: 754001152},
  1574 + Scaling: &cScaling{
  1575 + Orientation: &attrValString{Val: orientation[formatSet.XAxis.ReverseOrder]},
  1576 + Max: max,
  1577 + Min: min,
  1578 + },
  1579 + Delete: &attrValBool{Val: false},
  1580 + AxPos: &attrValString{Val: catAxPos[formatSet.XAxis.ReverseOrder]},
  1581 + NumFmt: &cNumFmt{
  1582 + FormatCode: "General",
  1583 + SourceLinked: true,
  1584 + },
  1585 + MajorTickMark: &attrValString{Val: "none"},
  1586 + MinorTickMark: &attrValString{Val: "none"},
  1587 + TickLblPos: &attrValString{Val: "nextTo"},
  1588 + SpPr: f.drawPlotAreaSpPr(),
  1589 + TxPr: f.drawPlotAreaTxPr(),
  1590 + CrossAx: &attrValInt{Val: 753999904},
  1591 + Crosses: &attrValString{Val: "autoZero"},
  1592 + Auto: &attrValBool{Val: true},
  1593 + LblAlgn: &attrValString{Val: "ctr"},
  1594 + LblOffset: &attrValInt{Val: 100},
  1595 + NoMultiLvlLbl: &attrValBool{Val: false},
  1596 + },
  1597 + }
  1598 + if formatSet.XAxis.MajorGridlines {
  1599 + axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
  1600 + }
  1601 + return axs
  1602 +}
  1603 +
  1604 +// drawPlotAreaValAx provides a function to draw the c:valAx element.
  1605 +func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs {
  1606 + min := &attrValFloat{Val: formatSet.YAxis.Minimum}
  1607 + max := &attrValFloat{Val: formatSet.YAxis.Maximum}
  1608 + if formatSet.YAxis.Minimum == 0 {
  1609 + min = nil
  1610 + }
  1611 + if formatSet.YAxis.Maximum == 0 {
  1612 + max = nil
  1613 + }
  1614 + axs := []*cAxs{
  1615 + {
  1616 + AxID: &attrValInt{Val: 753999904},
  1617 + Scaling: &cScaling{
  1618 + Orientation: &attrValString{Val: orientation[formatSet.YAxis.ReverseOrder]},
  1619 + Max: max,
  1620 + Min: min,
  1621 + },
  1622 + Delete: &attrValBool{Val: false},
  1623 + AxPos: &attrValString{Val: valAxPos[formatSet.YAxis.ReverseOrder]},
  1624 + NumFmt: &cNumFmt{
  1625 + FormatCode: chartValAxNumFmtFormatCode[formatSet.Type],
  1626 + SourceLinked: true,
  1627 + },
  1628 + MajorTickMark: &attrValString{Val: "none"},
  1629 + MinorTickMark: &attrValString{Val: "none"},
  1630 + TickLblPos: &attrValString{Val: "nextTo"},
  1631 + SpPr: f.drawPlotAreaSpPr(),
  1632 + TxPr: f.drawPlotAreaTxPr(),
  1633 + CrossAx: &attrValInt{Val: 754001152},
  1634 + Crosses: &attrValString{Val: "autoZero"},
  1635 + CrossBetween: &attrValString{Val: chartValAxCrossBetween[formatSet.Type]},
  1636 + },
  1637 + }
  1638 + if formatSet.YAxis.MajorGridlines {
  1639 + axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
  1640 + }
  1641 + if pos, ok := valTickLblPos[formatSet.Type]; ok {
  1642 + axs[0].TickLblPos.Val = pos
  1643 + }
  1644 + return axs
  1645 +}
  1646 +
  1647 +// drawPlotAreaSerAx provides a function to draw the c:serAx element.
  1648 +func (f *File) drawPlotAreaSerAx(formatSet *formatChart) []*cAxs {
  1649 + min := &attrValFloat{Val: formatSet.YAxis.Minimum}
  1650 + max := &attrValFloat{Val: formatSet.YAxis.Maximum}
  1651 + if formatSet.YAxis.Minimum == 0 {
  1652 + min = nil
  1653 + }
  1654 + if formatSet.YAxis.Maximum == 0 {
  1655 + max = nil
  1656 + }
  1657 + return []*cAxs{
  1658 + {
  1659 + AxID: &attrValInt{Val: 832256642},
  1660 + Scaling: &cScaling{
  1661 + Orientation: &attrValString{Val: orientation[formatSet.YAxis.ReverseOrder]},
  1662 + Max: max,
  1663 + Min: min,
  1664 + },
  1665 + Delete: &attrValBool{Val: false},
  1666 + AxPos: &attrValString{Val: catAxPos[formatSet.XAxis.ReverseOrder]},
  1667 + TickLblPos: &attrValString{Val: "nextTo"},
  1668 + SpPr: f.drawPlotAreaSpPr(),
  1669 + TxPr: f.drawPlotAreaTxPr(),
  1670 + CrossAx: &attrValInt{Val: 753999904},
  1671 + },
  1672 + }
  1673 +}
  1674 +
  1675 +// drawPlotAreaSpPr provides a function to draw the c:spPr element.
  1676 +func (f *File) drawPlotAreaSpPr() *cSpPr {
  1677 + return &cSpPr{
  1678 + Ln: &aLn{
  1679 + W: 9525,
  1680 + Cap: "flat",
  1681 + Cmpd: "sng",
  1682 + Algn: "ctr",
  1683 + SolidFill: &aSolidFill{
  1684 + SchemeClr: &aSchemeClr{
  1685 + Val: "tx1",
  1686 + LumMod: &attrValInt{Val: 15000},
  1687 + LumOff: &attrValInt{Val: 85000},
  1688 + },
  1689 + },
  1690 + },
  1691 + }
  1692 +}
  1693 +
  1694 +// drawPlotAreaTxPr provides a function to draw the c:txPr element.
  1695 +func (f *File) drawPlotAreaTxPr() *cTxPr {
  1696 + return &cTxPr{
  1697 + BodyPr: aBodyPr{
  1698 + Rot: -60000000,
  1699 + SpcFirstLastPara: true,
  1700 + VertOverflow: "ellipsis",
  1701 + Vert: "horz",
  1702 + Wrap: "square",
  1703 + Anchor: "ctr",
  1704 + AnchorCtr: true,
  1705 + },
  1706 + P: aP{
  1707 + PPr: &aPPr{
  1708 + DefRPr: aRPr{
  1709 + Sz: 900,
  1710 + B: false,
  1711 + I: false,
  1712 + U: "none",
  1713 + Strike: "noStrike",
  1714 + Kern: 1200,
  1715 + Baseline: 0,
  1716 + SolidFill: &aSolidFill{
  1717 + SchemeClr: &aSchemeClr{
  1718 + Val: "tx1",
  1719 + LumMod: &attrValInt{Val: 15000},
  1720 + LumOff: &attrValInt{Val: 85000},
  1721 + },
  1722 + },
  1723 + Latin: &aLatin{Typeface: "+mn-lt"},
  1724 + Ea: &aEa{Typeface: "+mn-ea"},
  1725 + Cs: &aCs{Typeface: "+mn-cs"},
  1726 + },
  1727 + },
  1728 + EndParaRPr: &aEndParaRPr{Lang: "en-US"},
  1729 + },
  1730 + }
  1731 +}
  1732 +
  1733 +// drawingParser provides a function to parse drawingXML. In order to solve
  1734 +// the problem that the label structure is changed after serialization and
  1735 +// deserialization, two different structures: decodeWsDr and encodeWsDr are
  1736 +// defined.
  1737 +func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
  1738 + if f.Drawings[path] == nil {
  1739 + content := xlsxWsDr{}
  1740 + content.A = NameSpaceDrawingML
  1741 + content.Xdr = NameSpaceDrawingMLSpreadSheet
  1742 + _, ok := f.XLSX[path]
  1743 + if ok { // Append Model
  1744 + decodeWsDr := decodeWsDr{}
  1745 + _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(path)), &decodeWsDr)
  1746 + content.R = decodeWsDr.R
  1747 + for _, v := range decodeWsDr.OneCellAnchor {
  1748 + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
  1749 + EditAs: v.EditAs,
  1750 + GraphicFrame: v.Content,
  1751 + })
  1752 + }
  1753 + for _, v := range decodeWsDr.TwoCellAnchor {
  1754 + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{
  1755 + EditAs: v.EditAs,
  1756 + GraphicFrame: v.Content,
  1757 + })
  1758 + }
  1759 + }
  1760 + f.Drawings[path] = &content
  1761 + }
  1762 + wsDr := f.Drawings[path]
  1763 + return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2
  1764 +}
  1765 +
  1766 +// addDrawingChart provides a function to add chart graphic frame by given
  1767 +// sheet, drawingXML, cell, width, height, relationship index and format sets.
  1768 +func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, formatSet *formatPicture) error {
  1769 + col, row, err := CellNameToCoordinates(cell)
  1770 + if err != nil {
  1771 + return err
  1772 + }
  1773 + colIdx := col - 1
  1774 + rowIdx := row - 1
  1775 +
  1776 + width = int(float64(width) * formatSet.XScale)
  1777 + height = int(float64(height) * formatSet.YScale)
  1778 + colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
  1779 + f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
  1780 + content, cNvPrID := f.drawingParser(drawingXML)
  1781 + twoCellAnchor := xdrCellAnchor{}
  1782 + twoCellAnchor.EditAs = formatSet.Positioning
  1783 + from := xlsxFrom{}
  1784 + from.Col = colStart
  1785 + from.ColOff = formatSet.OffsetX * EMU
  1786 + from.Row = rowStart
  1787 + from.RowOff = formatSet.OffsetY * EMU
  1788 + to := xlsxTo{}
  1789 + to.Col = colEnd
  1790 + to.ColOff = x2 * EMU
  1791 + to.Row = rowEnd
  1792 + to.RowOff = y2 * EMU
  1793 + twoCellAnchor.From = &from
  1794 + twoCellAnchor.To = &to
  1795 +
  1796 + graphicFrame := xlsxGraphicFrame{
  1797 + NvGraphicFramePr: xlsxNvGraphicFramePr{
  1798 + CNvPr: &xlsxCNvPr{
  1799 + ID: cNvPrID,
  1800 + Name: "Chart " + strconv.Itoa(cNvPrID),
  1801 + },
  1802 + },
  1803 + Graphic: &xlsxGraphic{
  1804 + GraphicData: &xlsxGraphicData{
  1805 + URI: NameSpaceDrawingMLChart,
  1806 + Chart: &xlsxChart{
  1807 + C: NameSpaceDrawingMLChart,
  1808 + R: SourceRelationship,
  1809 + RID: "rId" + strconv.Itoa(rID),
  1810 + },
  1811 + },
  1812 + },
  1813 + }
  1814 + graphic, _ := xml.Marshal(graphicFrame)
  1815 + twoCellAnchor.GraphicFrame = string(graphic)
  1816 + twoCellAnchor.ClientData = &xdrClientData{
  1817 + FLocksWithSheet: formatSet.FLocksWithSheet,
  1818 + FPrintsWithSheet: formatSet.FPrintsWithSheet,
  1819 + }
  1820 + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
  1821 + f.Drawings[drawingXML] = content
  1822 + return err
  1823 +}
  1 +tenets:
  2 + - import: codelingo/effective-go
  3 + - import: codelingo/code-review-comments
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import (
  13 + "errors"
  14 + "math"
  15 + "strings"
  16 +)
  17 +
  18 +// Define the default cell size and EMU unit of measurement.
  19 +const (
  20 + defaultColWidthPixels float64 = 64
  21 + defaultRowHeightPixels float64 = 20
  22 + EMU int = 9525
  23 +)
  24 +
  25 +// GetColVisible provides a function to get visible of a single column by given
  26 +// worksheet name and column name. For example, get visible state of column D
  27 +// in Sheet1:
  28 +//
  29 +// visiable, err := f.GetColVisible("Sheet1", "D")
  30 +//
  31 +func (f *File) GetColVisible(sheet, col string) (bool, error) {
  32 + visible := true
  33 + colNum, err := ColumnNameToNumber(col)
  34 + if err != nil {
  35 + return visible, err
  36 + }
  37 +
  38 + xlsx, err := f.workSheetReader(sheet)
  39 + if err != nil {
  40 + return false, err
  41 + }
  42 + if xlsx.Cols == nil {
  43 + return visible, err
  44 + }
  45 +
  46 + for c := range xlsx.Cols.Col {
  47 + colData := &xlsx.Cols.Col[c]
  48 + if colData.Min <= colNum && colNum <= colData.Max {
  49 + visible = !colData.Hidden
  50 + }
  51 + }
  52 + return visible, err
  53 +}
  54 +
  55 +// SetColVisible provides a function to set visible of a single column by given
  56 +// worksheet name and column name. For example, hide column D in Sheet1:
  57 +//
  58 +// err := f.SetColVisible("Sheet1", "D", false)
  59 +//
  60 +func (f *File) SetColVisible(sheet, col string, visible bool) error {
  61 + colNum, err := ColumnNameToNumber(col)
  62 + if err != nil {
  63 + return err
  64 + }
  65 + colData := xlsxCol{
  66 + Min: colNum,
  67 + Max: colNum,
  68 + Hidden: !visible,
  69 + CustomWidth: true,
  70 + }
  71 + xlsx, err := f.workSheetReader(sheet)
  72 + if err != nil {
  73 + return err
  74 + }
  75 + if xlsx.Cols == nil {
  76 + cols := xlsxCols{}
  77 + cols.Col = append(cols.Col, colData)
  78 + xlsx.Cols = &cols
  79 + return err
  80 + }
  81 + for v := range xlsx.Cols.Col {
  82 + if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
  83 + colData = xlsx.Cols.Col[v]
  84 + }
  85 + }
  86 + colData.Min = colNum
  87 + colData.Max = colNum
  88 + colData.Hidden = !visible
  89 + colData.CustomWidth = true
  90 + xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
  91 + return err
  92 +}
  93 +
  94 +// GetColOutlineLevel provides a function to get outline level of a single
  95 +// column by given worksheet name and column name. For example, get outline
  96 +// level of column D in Sheet1:
  97 +//
  98 +// level, err := f.GetColOutlineLevel("Sheet1", "D")
  99 +//
  100 +func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
  101 + level := uint8(0)
  102 + colNum, err := ColumnNameToNumber(col)
  103 + if err != nil {
  104 + return level, err
  105 + }
  106 + xlsx, err := f.workSheetReader(sheet)
  107 + if err != nil {
  108 + return 0, err
  109 + }
  110 + if xlsx.Cols == nil {
  111 + return level, err
  112 + }
  113 + for c := range xlsx.Cols.Col {
  114 + colData := &xlsx.Cols.Col[c]
  115 + if colData.Min <= colNum && colNum <= colData.Max {
  116 + level = colData.OutlineLevel
  117 + }
  118 + }
  119 + return level, err
  120 +}
  121 +
  122 +// SetColOutlineLevel provides a function to set outline level of a single
  123 +// column by given worksheet name and column name. The value of parameter
  124 +// 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2:
  125 +//
  126 +// err := f.SetColOutlineLevel("Sheet1", "D", 2)
  127 +//
  128 +func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
  129 + if level > 7 || level < 1 {
  130 + return errors.New("invalid outline level")
  131 + }
  132 + colNum, err := ColumnNameToNumber(col)
  133 + if err != nil {
  134 + return err
  135 + }
  136 + colData := xlsxCol{
  137 + Min: colNum,
  138 + Max: colNum,
  139 + OutlineLevel: level,
  140 + CustomWidth: true,
  141 + }
  142 + xlsx, err := f.workSheetReader(sheet)
  143 + if err != nil {
  144 + return err
  145 + }
  146 + if xlsx.Cols == nil {
  147 + cols := xlsxCols{}
  148 + cols.Col = append(cols.Col, colData)
  149 + xlsx.Cols = &cols
  150 + return err
  151 + }
  152 + for v := range xlsx.Cols.Col {
  153 + if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
  154 + colData = xlsx.Cols.Col[v]
  155 + }
  156 + }
  157 + colData.Min = colNum
  158 + colData.Max = colNum
  159 + colData.OutlineLevel = level
  160 + colData.CustomWidth = true
  161 + xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
  162 + return err
  163 +}
  164 +
  165 +// SetColStyle provides a function to set style of columns by given worksheet
  166 +// name, columns range and style ID.
  167 +//
  168 +// For example set style of column H on Sheet1:
  169 +//
  170 +// err = f.SetColStyle("Sheet1", "H", style)
  171 +//
  172 +// Set style of columns C:F on Sheet1:
  173 +//
  174 +// err = f.SetColStyle("Sheet1", "C:F", style)
  175 +//
  176 +func (f *File) SetColStyle(sheet, columns string, styleID int) error {
  177 + xlsx, err := f.workSheetReader(sheet)
  178 + if err != nil {
  179 + return err
  180 + }
  181 + var c1, c2 string
  182 + var min, max int
  183 + cols := strings.Split(columns, ":")
  184 + c1 = cols[0]
  185 + min, err = ColumnNameToNumber(c1)
  186 + if err != nil {
  187 + return err
  188 + }
  189 + if len(cols) == 2 {
  190 + c2 = cols[1]
  191 + max, err = ColumnNameToNumber(c2)
  192 + if err != nil {
  193 + return err
  194 + }
  195 + } else {
  196 + max = min
  197 + }
  198 + if max < min {
  199 + min, max = max, min
  200 + }
  201 + if xlsx.Cols == nil {
  202 + xlsx.Cols = &xlsxCols{}
  203 + }
  204 + var find bool
  205 + for idx, col := range xlsx.Cols.Col {
  206 + if col.Min == min && col.Max == max {
  207 + xlsx.Cols.Col[idx].Style = styleID
  208 + find = true
  209 + }
  210 + }
  211 + if !find {
  212 + xlsx.Cols.Col = append(xlsx.Cols.Col, xlsxCol{
  213 + Min: min,
  214 + Max: max,
  215 + Width: 9,
  216 + Style: styleID,
  217 + })
  218 + }
  219 + return nil
  220 +}
  221 +
  222 +// SetColWidth provides a function to set the width of a single column or
  223 +// multiple columns. For example:
  224 +//
  225 +// f := excelize.NewFile()
  226 +// err := f.SetColWidth("Sheet1", "A", "H", 20)
  227 +//
  228 +func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error {
  229 + min, err := ColumnNameToNumber(startcol)
  230 + if err != nil {
  231 + return err
  232 + }
  233 + max, err := ColumnNameToNumber(endcol)
  234 + if err != nil {
  235 + return err
  236 + }
  237 + if min > max {
  238 + min, max = max, min
  239 + }
  240 +
  241 + xlsx, err := f.workSheetReader(sheet)
  242 + if err != nil {
  243 + return err
  244 + }
  245 + col := xlsxCol{
  246 + Min: min,
  247 + Max: max,
  248 + Width: width,
  249 + CustomWidth: true,
  250 + }
  251 + if xlsx.Cols != nil {
  252 + xlsx.Cols.Col = append(xlsx.Cols.Col, col)
  253 + } else {
  254 + cols := xlsxCols{}
  255 + cols.Col = append(cols.Col, col)
  256 + xlsx.Cols = &cols
  257 + }
  258 + return err
  259 +}
  260 +
  261 +// positionObjectPixels calculate the vertices that define the position of a
  262 +// graphical object within the worksheet in pixels.
  263 +//
  264 +// +------------+------------+
  265 +// | A | B |
  266 +// +-----+------------+------------+
  267 +// | |(x1,y1) | |
  268 +// | 1 |(A1)._______|______ |
  269 +// | | | | |
  270 +// | | | | |
  271 +// +-----+----| OBJECT |-----+
  272 +// | | | | |
  273 +// | 2 | |______________. |
  274 +// | | | (B2)|
  275 +// | | | (x2,y2)|
  276 +// +-----+------------+------------+
  277 +//
  278 +// Example of an object that covers some of the area from cell A1 to B2.
  279 +//
  280 +// Based on the width and height of the object we need to calculate 8 vars:
  281 +//
  282 +// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2.
  283 +//
  284 +// We also calculate the absolute x and y position of the top left vertex of
  285 +// the object. This is required for images.
  286 +//
  287 +// The width and height of the cells that the object occupies can be
  288 +// variable and have to be taken into account.
  289 +//
  290 +// The values of col_start and row_start are passed in from the calling
  291 +// function. The values of col_end and row_end are calculated by
  292 +// subtracting the width and height of the object from the width and
  293 +// height of the underlying cells.
  294 +//
  295 +// colStart # Col containing upper left corner of object.
  296 +// x1 # Distance to left side of object.
  297 +//
  298 +// rowStart # Row containing top left corner of object.
  299 +// y1 # Distance to top of object.
  300 +//
  301 +// colEnd # Col containing lower right corner of object.
  302 +// x2 # Distance to right side of object.
  303 +//
  304 +// rowEnd # Row containing bottom right corner of object.
  305 +// y2 # Distance to bottom of object.
  306 +//
  307 +// width # Width of object frame.
  308 +// height # Height of object frame.
  309 +//
  310 +// xAbs # Absolute distance to left side of object.
  311 +// yAbs # Absolute distance to top side of object.
  312 +//
  313 +func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
  314 + xAbs := 0
  315 + yAbs := 0
  316 +
  317 + // Calculate the absolute x offset of the top-left vertex.
  318 + for colID := 1; colID <= col; colID++ {
  319 + xAbs += f.getColWidth(sheet, colID)
  320 + }
  321 + xAbs += x1
  322 +
  323 + // Calculate the absolute y offset of the top-left vertex.
  324 + // Store the column change to allow optimisations.
  325 + for rowID := 1; rowID <= row; rowID++ {
  326 + yAbs += f.getRowHeight(sheet, rowID)
  327 + }
  328 + yAbs += y1
  329 +
  330 + // Adjust start column for offsets that are greater than the col width.
  331 + for x1 >= f.getColWidth(sheet, col) {
  332 + x1 -= f.getColWidth(sheet, col)
  333 + col++
  334 + }
  335 +
  336 + // Adjust start row for offsets that are greater than the row height.
  337 + for y1 >= f.getRowHeight(sheet, row) {
  338 + y1 -= f.getRowHeight(sheet, row)
  339 + row++
  340 + }
  341 +
  342 + // Initialise end cell to the same as the start cell.
  343 + colEnd := col
  344 + rowEnd := row
  345 +
  346 + width += x1
  347 + height += y1
  348 +
  349 + // Subtract the underlying cell widths to find end cell of the object.
  350 + for width >= f.getColWidth(sheet, colEnd+1) {
  351 + colEnd++
  352 + width -= f.getColWidth(sheet, colEnd)
  353 + }
  354 +
  355 + // Subtract the underlying cell heights to find end cell of the object.
  356 + for height >= f.getRowHeight(sheet, rowEnd) {
  357 + height -= f.getRowHeight(sheet, rowEnd)
  358 + rowEnd++
  359 + }
  360 +
  361 + // The end vertices are whatever is left from the width and height.
  362 + x2 := width
  363 + y2 := height
  364 + return col, row, xAbs, yAbs, colEnd, rowEnd, x2, y2
  365 +}
  366 +
  367 +// getColWidth provides a function to get column width in pixels by given
  368 +// sheet name and column index.
  369 +func (f *File) getColWidth(sheet string, col int) int {
  370 + xlsx, _ := f.workSheetReader(sheet)
  371 + if xlsx.Cols != nil {
  372 + var width float64
  373 + for _, v := range xlsx.Cols.Col {
  374 + if v.Min <= col && col <= v.Max {
  375 + width = v.Width
  376 + }
  377 + }
  378 + if width != 0 {
  379 + return int(convertColWidthToPixels(width))
  380 + }
  381 + }
  382 + // Optimisation for when the column widths haven't changed.
  383 + return int(defaultColWidthPixels)
  384 +}
  385 +
  386 +// GetColWidth provides a function to get column width by given worksheet name
  387 +// and column index.
  388 +func (f *File) GetColWidth(sheet, col string) (float64, error) {
  389 + colNum, err := ColumnNameToNumber(col)
  390 + if err != nil {
  391 + return defaultColWidthPixels, err
  392 + }
  393 + xlsx, err := f.workSheetReader(sheet)
  394 + if err != nil {
  395 + return defaultColWidthPixels, err
  396 + }
  397 + if xlsx.Cols != nil {
  398 + var width float64
  399 + for _, v := range xlsx.Cols.Col {
  400 + if v.Min <= colNum && colNum <= v.Max {
  401 + width = v.Width
  402 + }
  403 + }
  404 + if width != 0 {
  405 + return width, err
  406 + }
  407 + }
  408 + // Optimisation for when the column widths haven't changed.
  409 + return defaultColWidthPixels, err
  410 +}
  411 +
  412 +// InsertCol provides a function to insert a new column before given column
  413 +// index. For example, create a new column before column C in Sheet1:
  414 +//
  415 +// err := f.InsertCol("Sheet1", "C")
  416 +//
  417 +func (f *File) InsertCol(sheet, col string) error {
  418 + num, err := ColumnNameToNumber(col)
  419 + if err != nil {
  420 + return err
  421 + }
  422 + return f.adjustHelper(sheet, columns, num, 1)
  423 +}
  424 +
  425 +// RemoveCol provides a function to remove single column by given worksheet
  426 +// name and column index. For example, remove column C in Sheet1:
  427 +//
  428 +// err := f.RemoveCol("Sheet1", "C")
  429 +//
  430 +// Use this method with caution, which will affect changes in references such
  431 +// as formulas, charts, and so on. If there is any referenced value of the
  432 +// worksheet, it will cause a file error when you open it. The excelize only
  433 +// partially updates these references currently.
  434 +func (f *File) RemoveCol(sheet, col string) error {
  435 + num, err := ColumnNameToNumber(col)
  436 + if err != nil {
  437 + return err
  438 + }
  439 +
  440 + xlsx, err := f.workSheetReader(sheet)
  441 + if err != nil {
  442 + return err
  443 + }
  444 + for rowIdx := range xlsx.SheetData.Row {
  445 + rowData := &xlsx.SheetData.Row[rowIdx]
  446 + for colIdx := range rowData.C {
  447 + colName, _, _ := SplitCellName(rowData.C[colIdx].R)
  448 + if colName == col {
  449 + rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)[:len(rowData.C)-1]
  450 + break
  451 + }
  452 + }
  453 + }
  454 + return f.adjustHelper(sheet, columns, num, -1)
  455 +}
  456 +
  457 +// convertColWidthToPixels provieds function to convert the width of a cell
  458 +// from user's units to pixels. Excel rounds the column width to the nearest
  459 +// pixel. If the width hasn't been set by the user we use the default value.
  460 +// If the column is hidden it has a value of zero.
  461 +func convertColWidthToPixels(width float64) float64 {
  462 + var padding float64 = 5
  463 + var pixels float64
  464 + var maxDigitWidth float64 = 7
  465 + if width == 0 {
  466 + return pixels
  467 + }
  468 + if width < 1 {
  469 + pixels = (width * 12) + 0.5
  470 + return math.Ceil(pixels)
  471 + }
  472 + pixels = (width*maxDigitWidth + 0.5) + padding
  473 + return math.Ceil(pixels)
  474 +}
  1 +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2 +// this source code is governed by a BSD-style license that can be found in
  3 +// the LICENSE file.
  4 +//
  5 +// Package excelize providing a set of functions that allow you to write to
  6 +// and read from XLSX files. Support reads and writes XLSX file generated by
  7 +// Microsoft Excel™ 2007 and later. Support save file without losing original
  8 +// charts of XLSX. This library needs Go version 1.10 or later.
  9 +
  10 +package excelize
  11 +
  12 +import (
  13 + "encoding/json"
  14 + "encoding/xml"
  15 + "fmt"
  16 + "strconv"
  17 + "strings"
  18 +)
  19 +
  20 +// parseFormatCommentsSet provides a function to parse the format settings of
  21 +// the comment with default value.
  22 +func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
  23 + format := formatComment{
  24 + Author: "Author:",
  25 + Text: " ",
  26 + }
  27 + err := json.Unmarshal([]byte(formatSet), &format)
  28 + return &format, err
  29 +}
  30 +
  31 +// GetComments retrieves all comments and returns a map of worksheet name to
  32 +// the worksheet comments.
  33 +func (f *File) GetComments() (comments map[string][]Comment) {
  34 + comments = map[string][]Comment{}
  35 + for n := range f.sheetMap {
  36 + if d := f.commentsReader("xl" + strings.TrimPrefix(f.getSheetComments(f.GetSheetIndex(n)), "..")); d != nil {
  37 + sheetComments := []Comment{}
  38 + for _, comment := range d.CommentList.Comment {
  39 + sheetComment := Comment{}
  40 + if comment.AuthorID < len(d.Authors) {
  41 + sheetComment.Author = d.Authors[comment.AuthorID].Author
  42 + }
  43 + sheetComment.Ref = comment.Ref
  44 + sheetComment.AuthorID = comment.AuthorID
  45 + if comment.Text.T != nil {
  46 + sheetComment.Text += *comment.Text.T
  47 + }
  48 + for _, text := range comment.Text.R {
  49 + sheetComment.Text += text.T
  50 + }
  51 + sheetComments = append(sheetComments, sheetComment)
  52 + }
  53 + comments[n] = sheetComments
  54 + }
  55 + }
  56 + return
  57 +}
  58 +
  59 +// getSheetComments provides the method to get the target comment reference by
  60 +// given worksheet index.
  61 +func (f *File) getSheetComments(sheetID int) string {
  62 + var rels = "xl/worksheets/_rels/sheet" + strconv.Itoa(sheetID) + ".xml.rels"
  63 + if sheetRels := f.relsReader(rels); sheetRels != nil {
  64 + for _, v := range sheetRels.Relationships {
  65 + if v.Type == SourceRelationshipComments {
  66 + return v.Target
  67 + }
  68 + }
  69 + }
  70 + return ""
  71 +}
  72 +
  73 +// AddComment provides the method to add comment in a sheet by given worksheet
  74 +// index, cell and format set (such as author and text). Note that the max
  75 +// author length is 255 and the max text length is 32512. For example, add a
  76 +// comment in Sheet1!$A$30:
  77 +//
  78 +// err := f.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
  79 +//
  80 +func (f *File) AddComment(sheet, cell, format string) error {
  81 + formatSet, err := parseFormatCommentsSet(format)
  82 + if err != nil {
  83 + return err
  84 + }
  85 + // Read sheet data.
  86 + xlsx, err := f.workSheetReader(sheet)
  87 + if err != nil {
  88 + return err
  89 + }
  90 + commentID := f.countComments() + 1
  91 + drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
  92 + sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
  93 + sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
  94 + if xlsx.LegacyDrawing != nil {
  95 + // The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
  96 + sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
  97 + commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
  98 + drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
  99 + } else {
  100 + // Add first comment for given sheet.
  101 + sheetPath, _ := f.sheetMap[trimSheetName(sheet)]
  102 + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
  103 + rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
  104 + f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
  105 + f.addSheetLegacyDrawing(sheet, rID)
  106 + }
  107 + commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
  108 + f.addComment(commentsXML, cell, formatSet)
  109 + var colCount int
  110 + for i, l := range strings.Split(formatSet.Text, "\n") {
  111 + if ll := len(l); ll > colCount {
  112 + if i == 0 {
  113 + ll += len(formatSet.Author)
  114 + }
  115 + colCount = ll
  116 + }
  117 + }
  118 + err = f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount)
  119 + if err != nil {
  120 + return err
  121 + }
  122 + f.addContentTypePart(commentID, "comments")
  123 + return err
  124 +}
  125 +
  126 +// addDrawingVML provides a function to create comment as
  127 +// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
  128 +func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) error {
  129 + col, row, err := CellNameToCoordinates(cell)
  130 + if err != nil {
  131 + return err
  132 + }
  133 + yAxis := col - 1
  134 + xAxis := row - 1
  135 + vml := f.VMLDrawing[drawingVML]
  136 + if vml == nil {
  137 + vml = &vmlDrawing{
  138 + XMLNSv: "urn:schemas-microsoft-com:vml",
  139 + XMLNSo: "urn:schemas-microsoft-com:office:office",
  140 + XMLNSx: "urn:schemas-microsoft-com:office:excel",
  141 + XMLNSmv: "http://macVmlSchemaUri",
  142 + Shapelayout: &xlsxShapelayout{
  143 + Ext: "edit",
  144 + IDmap: &xlsxIDmap{
  145 + Ext: "edit",
  146 + Data: commentID,
  147 + },
  148 + },
  149 + Shapetype: &xlsxShapetype{
  150 + ID: "_x0000_t202",
  151 + Coordsize: "21600,21600",
  152 + Spt: 202,
  153 + Path: "m0,0l0,21600,21600,21600,21600,0xe",
  154 + Stroke: &xlsxStroke{
  155 + Joinstyle: "miter",
  156 + },
  157 + VPath: &vPath{
  158 + Gradientshapeok: "t",
  159 + Connecttype: "miter",
  160 + },
  161 + },
  162 + }
  163 + }
  164 + sp := encodeShape{
  165 + Fill: &vFill{
  166 + Color2: "#fbfe82",
  167 + Angle: -180,
  168 + Type: "gradient",
  169 + Fill: &oFill{
  170 + Ext: "view",
  171 + Type: "gradientUnscaled",
  172 + },
  173 + },
  174 + Shadow: &vShadow{
  175 + On: "t",
  176 + Color: "black",
  177 + Obscured: "t",
  178 + },
  179 + Path: &vPath{
  180 + Connecttype: "none",
  181 + },
  182 + Textbox: &vTextbox{
  183 + Style: "mso-direction-alt:auto",
  184 + Div: &xlsxDiv{
  185 + Style: "text-align:left",
  186 + },
  187 + },
  188 + ClientData: &xClientData{
  189 + ObjectType: "Note",
  190 + Anchor: fmt.Sprintf(
  191 + "%d, 23, %d, 0, %d, %d, %d, 5",
  192 + 1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
  193 + AutoFill: "True",
  194 + Row: xAxis,
  195 + Column: yAxis,
  196 + },
  197 + }
  198 + s, _ := xml.Marshal(sp)
  199 + shape := xlsxShape{
  200 + ID: "_x0000_s1025",
  201 + Type: "#_x0000_t202",
  202 + Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
  203 + Fillcolor: "#fbf6d6",
  204 + Strokecolor: "#edeaa1",
  205 + Val: string(s[13 : len(s)-14]),
  206 + }
  207 + d := f.decodeVMLDrawingReader(drawingVML)
  208 + if d != nil {
  209 + for _, v := range d.Shape {
  210 + s := xlsxShape{
  211 + ID: "_x0000_s1025",
  212 + Type: "#_x0000_t202",
  213 + Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
  214 + Fillcolor: "#fbf6d6",
  215 + Strokecolor: "#edeaa1",
  216 + Val: v.Val,
  217 + }
  218 + vml.Shape = append(vml.Shape, s)
  219 + }
  220 + }
  221 + vml.Shape = append(vml.Shape, shape)
  222 + f.VMLDrawing[drawingVML] = vml
  223 + return err
  224 +}
  225 +
  226 +// addComment provides a function to create chart as xl/comments%d.xml by
  227 +// given cell and format sets.
  228 +func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
  229 + a := formatSet.Author
  230 + t := formatSet.Text
  231 + if len(a) > 255 {
  232 + a = a[0:255]
  233 + }
  234 + if len(t) > 32512 {
  235 + t = t[0:32512]
  236 + }
  237 + comments := f.commentsReader(commentsXML)
  238 + if comments == nil {
  239 + comments = &xlsxComments{
  240 + Authors: []xlsxAuthor{
  241 + {
  242 + Author: formatSet.Author,
  243 + },
  244 + },
  245 + }
  246 + }
  247 + defaultFont := f.GetDefaultFont()
  248 + cmt := xlsxComment{
  249 + Ref: cell,
  250 + AuthorID: 0,
  251 + Text: xlsxText{
  252 + R: []xlsxR{
  253 + {
  254 + RPr: &xlsxRPr{
  255 + B: " ",
  256 + Sz: &attrValFloat{Val: 9},
  257 + Color: &xlsxColor{
  258 + Indexed: 81,
  259 + },
  260 + RFont: &attrValString{Val: defaultFont},
  261 + Family: &attrValInt{Val: 2},
  262 + },
  263 + T: a,
  264 + },
  265 + {
  266 + RPr: &xlsxRPr{
  267 + Sz: &attrValFloat{Val: 9},
  268 + Color: &xlsxColor{
  269 + Indexed: 81,
  270 + },
  271 + RFont: &attrValString{Val: defaultFont},
  272 + Family: &attrValInt{Val: 2},
  273 + },
  274 + T: t,
  275 + },
  276 + },
  277 + },
  278 + }
  279 + comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
  280 + f.Comments[commentsXML] = comments
  281 +}
  282 +
  283 +// countComments provides a function to get comments files count storage in
  284 +// the folder xl.
  285 +func (f *File) countComments() int {
  286 + c1, c2 := 0, 0
  287 + for k := range f.XLSX {
  288 + if strings.Contains(k, "xl/comments") {
  289 + c1++
  290 + }
  291 + }
  292 + for rel := range f.Comments {
  293 + if strings.Contains(rel, "xl/comments") {
  294 + c2++
  295 + }
  296 + }
  297 + if c1 < c2 {
  298 + return c2
  299 + }
  300 + return c1
  301 +}
  302 +
  303 +// decodeVMLDrawingReader provides a function to get the pointer to the
  304 +// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
  305 +func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
  306 + if f.DecodeVMLDrawing[path] == nil {
  307 + c, ok := f.XLSX[path]
  308 + if ok {
  309 + d := decodeVmlDrawing{}
  310 + _ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
  311 + f.DecodeVMLDrawing[path] = &d
  312 + }
  313 + }
  314 + return f.DecodeVMLDrawing[path]
  315 +}
  316 +
  317 +// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
  318 +// after serialize structure.
  319 +func (f *File) vmlDrawingWriter() {
  320 + for path, vml := range f.VMLDrawing {
  321 + if vml != nil {
  322 + v, _ := xml.Marshal(vml)
  323 + f.XLSX[path] = v
  324 + }
  325 + }
  326 +}
  327 +
  328 +// commentsReader provides a function to get the pointer to the structure
  329 +// after deserialization of xl/comments%d.xml.
  330 +func (f *File) commentsReader(path string) *xlsxComments {
  331 + if f.Comments[path] == nil {
  332 + content, ok := f.XLSX[path]
  333 + if ok {
  334 + c := xlsxComments{}
  335 + _ = xml.Unmarshal(namespaceStrictToTransitional(content), &c)
  336 + f.Comments[path] = &c
  337 + }
  338 + }
  339 + return f.Comments[path]
  340 +}
  341 +
  342 +// commentsWriter provides a function to save xl/comments%d.xml after
  343 +// serialize structure.
  344 +func (f *File) commentsWriter() {
  345 + for path, c := range f.Comments {
  346 + if c != nil {
  347 + v, _ := xml.Marshal(c)
  348 + f.saveFileList(path, v)
  349 + }
  350 + }
  351 +}