Repository: qax-os/excelize
Branch: master
Commit: df428826c02b
Files: 115
Total size: 3.4 MB
Directory structure:
gitextract_jqjalinp/
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── SECURITY.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── codeql-analysis.yml
│ └── go.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_zh.md
├── adjust.go
├── adjust_test.go
├── calc.go
├── calc_test.go
├── calcchain.go
├── calcchain_test.go
├── cell.go
├── cell_test.go
├── chart.go
├── chart_test.go
├── col.go
├── col_test.go
├── crypt.go
├── crypt_test.go
├── datavalidation.go
├── datavalidation_test.go
├── date.go
├── date_test.go
├── docProps.go
├── docProps_test.go
├── drawing.go
├── drawing_test.go
├── errors.go
├── errors_test.go
├── excelize.go
├── excelize_test.go
├── file.go
├── file_test.go
├── go.mod
├── go.sum
├── hsl.go
├── lib.go
├── lib_test.go
├── merge.go
├── merge_test.go
├── numfmt.go
├── numfmt_test.go
├── picture.go
├── picture_test.go
├── pivotTable.go
├── pivotTable_test.go
├── rows.go
├── rows_test.go
├── shape.go
├── shape_test.go
├── sheet.go
├── sheet_test.go
├── sheetpr.go
├── sheetpr_test.go
├── sheetview.go
├── sheetview_test.go
├── slicer.go
├── slicer_test.go
├── sparkline.go
├── sparkline_test.go
├── stream.go
├── stream_test.go
├── styles.go
├── styles_test.go
├── table.go
├── table_test.go
├── templates.go
├── test/
│ ├── BadWorkbook.xlsx
│ ├── Book1.xlsx
│ ├── CalcChain.xlsx
│ ├── MergeCell.xlsx
│ ├── OverflowNumericCell.xlsx
│ ├── SharedStrings.xlsx
│ ├── encryptAES.xlsx
│ ├── encryptSHA1.xlsx
│ └── images/
│ ├── excel.emf
│ ├── excel.emz
│ ├── excel.tif
│ ├── excel.wmf
│ └── excel.wmz
├── vml.go
├── vmlDrawing.go
├── vml_test.go
├── workbook.go
├── workbook_test.go
├── xmlApp.go
├── xmlCalcChain.go
├── xmlChart.go
├── xmlChartSheet.go
├── xmlComments.go
├── xmlContentTypes.go
├── xmlCore.go
├── xmlCustom.go
├── xmlDecodeDrawing.go
├── xmlDrawing.go
├── xmlMetaData.go
├── xmlPivotCache.go
├── xmlPivotTable.go
├── xmlSharedStrings.go
├── xmlSlicers.go
├── xmlStyles.go
├── xmlTable.go
├── xmlTheme.go
├── xmlWorkbook.go
└── xmlWorksheet.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others’ private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders 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, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [xuri.me](https://xuri.me). All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to excelize
Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#community-guidelines) before you
start participating.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#community-guidelines)
## Reporting security issues
The excelize maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[xuri.me](https://xuri.me).
Security reports are greatly appreciated and we will publicly thank you for them.
We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [our issue database](https://github.com/xuri/excelize/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, you can use the "subscribe" button to get notified on
updates. Do *not* leave random "+1" or "I have this too" comments, as they
only clutter the discussion, and don't help resolving it. However, if you
have ways to reproduce the issue or have additional information that may help
resolving the issue, please leave a comment.
When reporting issues, always include the output of `go env`.
Also include the steps required to reproduce the problem if possible and
applicable. This information will help us review and fix your issue faster.
When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com).
Don't forget to remove sensitive data from your logfiles before posting (you can
replace those parts with "REDACTED").
## Quick contribution tips and guidelines
This section gives the experienced contributor some tips and guidelines.
### Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it. Any significant improvement should be
documented as [a GitHub issue](https://github.com/xuri/excelize/issues) before
anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged!
### Design and cleanup proposals
You can propose new designs for existing excelize features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project.
We try hard to keep excelize lean and focused. Excelize can't do everything for
everybody. This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* excelize.
### Conventions
Fork the repository and make changes on your fork in a feature branch:
* If it's a bug fix branch, name it XXXX-something where XXXX is the number of
the issue.
* If it's a feature branch, create an enhancement issue to announce
your intentions, and name it XXXX-something where XXXX is the number of the
issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test on your branch before
submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
### Successful Changes
Before contributing large or high impact changes, make the effort to coordinate
with the maintainers of the project before submitting a pull request. This
prevents you from doing extra work that may or may not be merged.
Large PRs that are just submitted without any prior communication is unlikely
to be successful.
While pull requests are the methodology for submitting changes to code, changes
are much more likely to be accepted if they are accompanied by additional
engineering work. While we don't define this explicitly, most of these goals
are accomplished through the communication of the design goals and subsequent
solutions. Oftentimes, it helps to first state the problem before presenting
solutions.
Typically, the best methods of accomplishing this are to submit an issue,
stating the problem. This issue can include a problem statement and a
checklist with requirements. If solutions are proposed, alternatives should be
listed and eliminated. Even if the criteria for elimination of a solution is
frivolous, say so.
Larger changes typically work best with design documents. These are focused on
providing context to the design at the time the feature was conceived and can
inform future documentation contributions.
### Commit Messages
Commit messages must start with a capitalized and short summary
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Commit messages should follow best practices, including explaining the context
of the problem and how it was solved, including in caveats or follow-up changes
required. They should tell the story of the change and provide readers
understanding of what led to it.
In practice, the best approach to maintaining a nice commit message is to
leverage a `git add -p` and `git commit --amend` to formulate a solid
changeset. This allows one to piece together a change, as information becomes
available.
If you squash a series of commits, don't just submit that. Re-write the commit
message, as if the series of commits was a single stroke of brilliance.
That said, there is no requirement to have a single commit for a PR, as long as
each commit tells the story. For example, if there is a feature that requires a
package, it might make sense to have the package in a separate commit then have
a subsequent commit that uses it.
Remember, you're telling part of the story with the commit message. Don't make
your chapter weird.
### Review
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
Pull requests must be cleanly rebased on top of master without multiple branches
mixed into the PR.
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
After every commit, make sure the test passes. Include documentation
changes in the same pull request so that a revert would remove all traces of
the feature or fix.
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
close an issue. Including references automatically closes the issue on a merge.
Please see the [Coding Style](#coding-style) for further guidelines.
### Merge approval
The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance.
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](https://developercertificate.org)):
```text
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
```text
Signed-off-by: Ri Xu https://xuri.me
```
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
### How can I become a maintainer
First, all maintainers have 3 things
* They share responsibility in the project's success.
* They have made a long-term, recurring time investment to improve the project.
* They spend that time doing whatever needs to be done, not necessarily what
is the most interesting or fun.
Maintainers are often under-appreciated, because their work is harder to appreciate.
It's easy to appreciate a really cool and technically advanced feature. It's harder
to appreciate the absence of bugs, the slow but steady improvement in stability,
or the reliability of a release process. But those things distinguish a good
project from a great one.
Don't forget: being a maintainer is a time investment. Make sure you
will have time to make yourself available. You don't have to be a
maintainer to make a difference on the project!
If you want to become a maintainer, contact [xuri.me](https://xuri.me) and given an introduction of you.
## Community guidelines
We want to keep the community awesome, growing and collaborative. We need
your help to keep it that way. To help with this we've come up with some general
guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members:
no regional, racial, gender, or other abuse will be tolerated. We like
nice people way better than mean ones!
* Encourage diversity and participation: Make everyone in our community feel
welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break
the law.
* Stay on topic: Make sure that you are posting to the correct channel and
avoid off-topic discussions. Remember when you update an issue or respond
to an email you are potentially sending to a large number of people. Please
consider this before you update. Also remember that nobody likes spam.
* Don't send email to the maintainers: There's no need to send email to the
maintainers to ask them to investigate an issue or to take a look at a
pull request. Instead of sending an email, GitHub mentions should be
used to ping maintainers to review a pull request, a proposal or an
issue.
### Guideline violations — 3 strikes method
The point of this section is not to find opportunities to punish people, but we
do need a fair way to deal with people who are making our community suck.
1. First occurrence: We'll give you a friendly, but public reminder that the
behavior is inappropriate according to our guidelines.
2. Second occurrence: We will send you a private message with a warning that
any additional violations will result in removal from the community.
3. Third occurrence: Depending on the violation, we may need to delete or ban
your account.
**Notes:**
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
have spam all over the place.
* Violations are forgiven after 6 months of good behavior, and we won't hold a
grudge.
* People who commit minor infractions will get some education, rather than
hammering them in the 3 strikes process.
* The rules apply equally to everyone in the community, no matter how much
you've contributed.
* Extreme violations of a threatening, abusive, destructive or illegal nature
will be addressed immediately and are not subject to 3 strikes or forgiveness.
* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of
appeals, we know that mistakes happen, and we'll work with you to come up with a
fair solution if there has been a misunderstanding.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`go vet`](https://pkg.go.dev/cmd/vet).
3. All code should follow the guidelines covered in [Effective
Go](https://go.dev/doc/effective_go) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant its own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](https://go.dev/doc/effective_go). The
[Go Blog](https://go.dev/blog/) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.
## Code Review Comments and Effective Go Guidelines
[CodeLingo](https://www.codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://go.dev/doc/effective_go) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
### Package Comment
Every package should have a package comment, a block comment preceding the package clause.
For multi-file packages, the package comment only needs to be present in one file, and any one will do.
The package comment should introduce the package and provide information relevant to the package as a
whole. It will appear first on the godoc page and should set up the detailed documentation that follows.
### Single Method Interface Name
By convention, one-method interfaces are named by the method name plus an -er suffix
or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
There are a number of such names and it's productive to honor them and the function names they capture.
Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion,
don't give your method one of those names unless it has the same signature and meaning. Conversely,
if your type implements a method with the same meaning as a method on a well-known type, give it the
same name and signature; call your string-converter method String not ToString.
### Avoid Annotations in Comments
Comments do not need extra formatting such as banners of stars. The generated output
may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc,
like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other
annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc
does do is to display indented text in a fixed-width font, suitable for program snippets.
The package comment for the fmt package uses this to good effect.
### Comment First Word as Subject
Doc comments work best as complete sentences, which allow a wide variety of automated presentations.
The first sentence should be a one-sentence summary that starts with the name being declared.
### Good Package Name
It's helpful if everyone using the package can use the same name
to refer to its contents, which implies that the package name should
be good: short, concise, and evocative. By convention, packages are
given lower case, single-word names; there should be no need for
underscores or mixedCaps. Err on the side of brevity, since everyone
using your package will be typing that name. And don't worry about
collisions a priori. The package name is only the default name for
imports; it need not be unique across all source code, and in the
rare case of a collision the importing package can choose a different
name to use locally. In any case, confusion is rare because the file
name in the import determines just which package is being used.
### Avoid Renaming Imports
Avoid renaming imports except to avoid a name collision; good package names
should not require renaming. In the event of collision, prefer to rename the
most local or project-specific import.
### Context as First Argument
Values of the context.Context type carry security credentials, tracing information,
deadlines, and cancellation signals across API and process boundaries. Go programs
pass Contexts explicitly along the entire function call chain from incoming RPCs
and HTTP requests to outgoing requests.
Most functions that use a Context should accept it as their first parameter.
### Do Not Discard Errors
Do not discard errors using _ variables. If a function returns an error,
check it to make sure the function succeeded. Handle the error, return it, or,
in truly exceptional situations, panic.
### Go Error Format
Error strings should not be capitalized (unless beginning with proper nouns
or acronyms) or end with punctuation, since they are usually printed following
other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"),
so that log.Printf("Reading %s: %v", filename, err) formats without a spurious
capital letter mid-message. This does not apply to logging, which is implicitly
line-oriented and not combined inside other messages.
### Use Crypto Rand
Do not use package math/rand to generate keys, even
throwaway ones. Unseeded, the generator is completely predictable.
Seeded with time.Nanoseconds(), there are just a few bits of entropy.
Instead, use crypto/rand's Reader, and if you need text, print to
hexadecimal or base64.
================================================
FILE: .github/FUNDING.yml
================================================
github: xuri
open_collective: excelize
patreon: xuri
ko_fi: xurime
liberapay: xuri
issuehunt: xuri
custom: https://www.paypal.com/paypalme/xuri
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
body:
- type: markdown
attributes:
value: |
If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead.
- type: textarea
id: description
attributes:
label: Description
description: Briefly describe the problem you are having in a few paragraphs.
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Steps to reproduce the issue
description: Explain how to cause the issue in the provided reproduction.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: received
attributes:
label: Describe the results you received
validations:
required: true
- type: textarea
id: expected
attributes:
label: Describe the results you expected
validations:
required: true
- type: input
id: go-version
attributes:
label: Go version
description: |
Output of `go version`:
placeholder: e.g. 1.23.4
validations:
required: true
- type: input
id: excelize-version
attributes:
label: Excelize version or commit ID
description: |
Which version of Excelize are you using?
placeholder: e.g. 2.9.0
validations:
required: true
- type: textarea
id: env
attributes:
label: Environment
description: Environment details (OS, Microsoft Excel™ version, physical, etc.)
render: shell
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
required: true
- label: The provided reproduction is a minimal reproducible example of the bug.
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for this project
body:
- type: markdown
attributes:
value: |
If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead.
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature that you would like added
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Any other context or screenshots about the feature request here?
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Check that there isn't already an issue that requests the same feature to avoid creating a duplicate.
required: true
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
# PR Details
## Description
## Related Issue
## Motivation and Context
## How Has This Been Tested
## Types of changes
- [ ] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
## Supported Versions
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.
## Reporting a Vulnerability
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!
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '0 6 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
language: ['go']
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
================================================
FILE: .github/workflows/go.yml
================================================
on: [push, pull_request]
name: build
jobs:
test:
strategy:
matrix:
go-version: [1.25.x, 1.26.x]
os: [ubuntu-24.04, macos-15-intel, macos-26, windows-latest]
targetplatform: [x86, x64]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
cache: false
- name: Checkout code
uses: actions/checkout@v6
- name: Get dependencies
run: |
env GO111MODULE=on go vet ./...
- name: Build
run: go build -v .
- name: Build on ARM
if: runner.os == 'Linux'
run: |
GOARCH=arm GOARM=5 go build .
GOARCH=arm GOARM=6 go build .
GOARCH=arm GOARM=7 go build .
GOARCH=arm64 go build .
GOARCH=arm64 GOOS=android go build .
- name: Test
run: env GO111MODULE=on go test -v -timeout 60m -race ./... -coverprofile='coverage.txt' -covermode=atomic
- name: Codecov
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: coverage.txt
flags: unittests
name: codecov-umbrella
================================================
FILE: .gitignore
================================================
.DS_Store
.idea
*.json
*.out
*.test
~$*.xlsx
test/*.png
test/BadWorkbook.SaveAsEmptyStruct.xlsx
test/Encryption*.xlsx
test/excelize-*
test/Test*.xlam
test/Test*.xlsm
test/Test*.xlsx
test/Test*.xltm
test/Test*.xltx
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2016-2026 The excelize Authors.
Copyright (c) 2011-2017 Geoffrey J. Teale
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================

# Excelize
## Introduction
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.25.0 or later. The full docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/).
## Basic Usage
### Installation
```bash
go get github.com/xuri/excelize
```
- If your packages are managed using [Go Modules](https://go.dev/blog/using-go-modules), please install with following command.
```bash
go get github.com/xuri/excelize/v2
```
### Create spreadsheet
Here is a minimal example usage that will create spreadsheet file.
```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// Create a new sheet.
index, err := f.NewSheet("Sheet2")
if err != nil {
fmt.Println(err)
return
}
// Set value of a cell.
f.SetCellValue("Sheet2", "A2", "Hello world.")
f.SetCellValue("Sheet1", "B2", 100)
// Set active sheet of the workbook.
f.SetActiveSheet(index)
// Save spreadsheet by the given path.
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
```
### Reading spreadsheet
The following constitutes the bare to read a spreadsheet document.
```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
// Close the spreadsheet.
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// Get value from cell by given worksheet name and cell reference.
cell, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(cell)
// Get all the rows in the Sheet1.
rows, err := f.GetRows("Sheet1")
if err != nil {
fmt.Println(err)
return
}
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
}
```
### Add chart to spreadsheet file
With Excelize chart generation and management is as easy as a few lines of code. You can build charts based on data in your worksheet or generate charts without any data in your worksheet at all.

```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
for idx, row := range [][]interface{}{
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
} {
cell, err := excelize.CoordinatesToCellName(1, idx+1)
if err != nil {
fmt.Println(err)
return
}
f.SetSheetRow("Sheet1", cell, &row)
}
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
Type: excelize.Col3DClustered,
Series: []excelize.ChartSeries{
{
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: []excelize.RichTextRun{
{
Text: "Fruit 3D Clustered Column Chart",
},
},
}); err != nil {
fmt.Println(err)
return
}
// Save spreadsheet by the given path.
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
```
### Add picture to spreadsheet file
```go
package main
import (
"fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
// Close the spreadsheet.
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// Insert a picture.
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
fmt.Println(err)
}
// Insert a picture to worksheet with scaling.
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
&excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
fmt.Println(err)
}
// Insert a picture offset in the cell with printing support.
enable, disable := true, false
if err := f.AddPicture("Sheet1", "H2", "image.gif",
&excelize.GraphicOptions{
PrintObject: &enable,
LockAspectRatio: false,
OffsetX: 15,
OffsetY: 10,
Locked: &disable,
}); err != nil {
fmt.Println(err)
}
// Save the spreadsheet with the origin path.
if err = f.Save(); err != nil {
fmt.Println(err)
}
}
```
## Contributing
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](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/).
## Licenses
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).
The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
The Go gopher was created by [Renee French](https://go.dev/doc/gopher/README). Licensed under the [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/).
## Gold Sponsors
================================================
FILE: README_zh.md
================================================

# Excelize
## 简介
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写函数,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.25.0 或更高版本。完整的使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)。
## 快速上手
### 安装
```bash
go get github.com/xuri/excelize
```
- 如果您使用 [Go Modules](https://go.dev/blog/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。
```bash
go get github.com/xuri/excelize/v2
```
### 创建 Excel 文档
下面是一个创建 Excel 文档的简单例子:
```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 创建一个工作表
index, err := f.NewSheet("Sheet2")
if err != nil {
fmt.Println(err)
return
}
// 设置单元格的值
f.SetCellValue("Sheet2", "A2", "Hello world.")
f.SetCellValue("Sheet1", "B2", 100)
// 设置工作簿的默认工作表
f.SetActiveSheet(index)
// 根据指定路径保存文件
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
```
### 读取 Excel 文档
下面是读取 Excel 文档的例子:
```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
// 关闭工作簿
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 获取工作表中指定单元格的值
cell, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(cell)
// 获取 Sheet1 上所有单元格
rows, err := f.GetRows("Sheet1")
if err != nil {
fmt.Println(err)
return
}
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
}
```
### 在 Excel 文档中创建图表
使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。

```go
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
for idx, row := range [][]interface{}{
{nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
{"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
} {
cell, err := excelize.CoordinatesToCellName(1, idx+1)
if err != nil {
fmt.Println(err)
return
}
f.SetSheetRow("Sheet1", cell, &row)
}
if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
Type: excelize.Col3DClustered,
Series: []excelize.ChartSeries{
{
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: []excelize.RichTextRun{
{
Text: "Fruit 3D Clustered Column Chart",
},
},
}); err != nil {
fmt.Println(err)
return
}
// 根据指定路径保存文件
if err := f.SaveAs("Book1.xlsx"); err != nil {
fmt.Println(err)
}
}
```
### 向 Excel 文档中插入图片
```go
package main
import (
"fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/xuri/excelize/v2"
)
func main() {
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
// 关闭工作簿
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 插入图片
if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的缩放比例
if err := f.AddPicture("Sheet1", "D2", "image.jpg",
&excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的打印属性
enable, disable := true, false
if err := f.AddPicture("Sheet1", "H2", "image.gif",
&excelize.GraphicOptions{
PrintObject: &enable,
LockAspectRatio: false,
OffsetX: 15,
OffsetY: 10,
Locked: &disable,
}); err != nil {
fmt.Println(err)
}
// 保存工作簿
if err = f.Save(); err != nil {
fmt.Println(err)
}
}
```
## 社区合作
欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)。
## 开源许可
本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。
Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
Go gopher 由 [Renee French](https://go.dev/doc/gopher/README) 创作,遵循 [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/) 创作共用授权条款。
## 金牌赞助商
================================================
FILE: adjust.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
"strconv"
"strings"
"unicode"
"github.com/xuri/efp"
)
type adjustDirection bool
const (
columns adjustDirection = false
rows adjustDirection = true
)
// adjustHelperFunc defines functions to adjust helper.
var adjustHelperFunc = [9]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustConditionalFormats(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDataValidations(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDefinedNames(sheet, dir, num, offset)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDrawings(ws, sheet, dir, num, offset)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustAutoFilter(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustTable(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID)
},
}
// adjustHelper provides a function to adjust rows and columns dimensions,
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
// columns.
//
// sheet: Worksheet name that we're editing
// column: Index number of the column we're inserting/deleting before
// row: Index number of the row we're inserting/deleting before
// offset: Number of rows/column to insert/delete negative values indicate deletion
//
// TODO: adjustComments, adjustPageBreaks, adjustProtectedCells
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
f.clearCalcCache()
sheetID := f.getSheetID(sheet)
if dir == rows {
err = f.adjustRowDimensions(sheet, ws, num, offset)
} else {
err = f.adjustColDimensions(sheet, ws, num, offset)
}
if err != nil {
return err
}
f.adjustHyperlinks(ws, sheet, dir, num, offset)
ws.checkSheet()
_ = ws.checkRow()
for _, fn := range adjustHelperFunc {
if err := fn(f, ws, sheet, dir, num, offset, sheetID); err != nil {
return err
}
}
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
ws.MergeCells = nil
}
return nil
}
// adjustCols provides a function to update column style when inserting or
// deleting columns.
func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error {
if ws.Cols == nil {
return nil
}
for i := 0; i < len(ws.Cols.Col); i++ {
if offset > 0 {
if ws.Cols.Col[i].Min >= col {
if ws.Cols.Col[i].Min += offset; ws.Cols.Col[i].Min > MaxColumns {
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
i--
continue
}
}
if ws.Cols.Col[i].Max >= col || ws.Cols.Col[i].Max+1 == col {
if ws.Cols.Col[i].Max += offset; ws.Cols.Col[i].Max > MaxColumns {
ws.Cols.Col[i].Max = MaxColumns
}
}
continue
}
if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
i--
continue
}
if ws.Cols.Col[i].Min > col {
ws.Cols.Col[i].Min += offset
}
if ws.Cols.Col[i].Max >= col {
ws.Cols.Col[i].Max += offset
}
}
if len(ws.Cols.Col) == 0 {
ws.Cols = nil
}
return nil
}
// adjustColDimensions provides a function to update column dimensions when
// inserting or deleting rows or columns.
func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset int) error {
for rowIdx := range ws.SheetData.Row {
for _, v := range ws.SheetData.Row[rowIdx].C {
if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol {
if newCol := cellCol + offset; newCol > 0 && newCol > MaxColumns {
return ErrColumnNumber
}
}
}
}
for _, sheetN := range f.GetSheetList() {
worksheet, err := f.workSheetReader(sheetN)
if err != nil {
if err.Error() == newNotWorksheetError(sheetN).Error() {
continue
}
return err
}
for rowIdx := range worksheet.SheetData.Row {
for colIdx, v := range worksheet.SheetData.Row[rowIdx].C {
if cellCol, cellRow, _ := CellNameToCoordinates(v.R); sheetN == sheet && col <= cellCol {
if newCol := cellCol + offset; newCol > 0 {
worksheet.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
}
}
if err := f.adjustFormula(sheet, sheetN, &worksheet.SheetData.Row[rowIdx].C[colIdx], columns, col, offset, false); err != nil {
return err
}
}
}
}
return f.adjustCols(ws, col, offset)
}
// adjustRowDimensions provides a function to update row dimensions when
// inserting or deleting rows or columns.
func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset int) error {
for _, sheetN := range f.GetSheetList() {
if sheetN == sheet {
continue
}
worksheet, err := f.workSheetReader(sheetN)
if err != nil {
if err.Error() == newNotWorksheetError(sheetN).Error() {
continue
}
return err
}
numOfRows := len(worksheet.SheetData.Row)
for i := 0; i < numOfRows; i++ {
r := &worksheet.SheetData.Row[i]
if err = f.adjustSingleRowFormulas(sheet, sheetN, r, row, offset, false); err != nil {
return err
}
}
}
totalRows := len(ws.SheetData.Row)
if totalRows == 0 {
return nil
}
lastRow := &ws.SheetData.Row[totalRows-1]
if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow > TotalRows {
return ErrMaxRows
}
numOfRows := len(ws.SheetData.Row)
for i := 0; i < numOfRows; i++ {
r := &ws.SheetData.Row[i]
if newRow := r.R + offset; r.R >= row && newRow > 0 {
r.adjustSingleRowDimensions(offset)
}
if err := f.adjustSingleRowFormulas(sheet, sheet, r, row, offset, false); err != nil {
return err
}
}
return nil
}
// adjustSingleRowDimensions provides a function to adjust single row dimensions.
func (r *xlsxRow) adjustSingleRowDimensions(offset int) {
r.R += offset
for i, col := range r.C {
colName, _, _ := SplitCellName(col.R)
r.C[i].R, _ = JoinCellName(colName, r.R)
}
}
// adjustSingleRowFormulas provides a function to adjust single row formulas.
func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, offset int, si bool) error {
for i := 0; i < len(r.C); i++ {
if err := f.adjustFormula(sheet, sheetN, &r.C[i], rows, num, offset, si); err != nil {
return err
}
}
return nil
}
// adjustCellRef provides a function to adjust cell reference.
func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset int) (string, error) {
var SQRef []string
applyOffset := func(coordinates []int, idx1, idx2, maxVal int) []int {
if coordinates[idx1] >= num {
coordinates[idx1] += offset
}
if coordinates[idx2] >= num {
if coordinates[idx2] += offset; coordinates[idx2] > maxVal {
coordinates[idx2] = maxVal
}
}
return coordinates
}
for _, ref := range strings.Split(cellRef, " ") {
if !strings.Contains(ref, ":") {
ref += ":" + ref
}
coordinates, err := rangeRefToCoordinates(ref)
if err != nil {
return "", err
}
if dir == columns {
if offset < 0 && coordinates[0] == coordinates[2] && num == coordinates[0] {
continue
}
coordinates = applyOffset(coordinates, 0, 2, MaxColumns)
} else {
if offset < 0 && coordinates[1] == coordinates[3] && num == coordinates[1] {
continue
}
coordinates = applyOffset(coordinates, 1, 3, TotalRows)
}
if ref, err = coordinatesToRangeRef(coordinates); err != nil {
return "", err
}
SQRef = append(SQRef, ref)
}
return strings.Join(SQRef, " "), nil
}
// adjustFormula provides a function to adjust formula reference and shared
// formula reference.
func (f *File) adjustFormula(sheet, sheetN string, cell *xlsxC, dir adjustDirection, num, offset int, si bool) error {
var err error
if cell.f != "" {
if cell.f, err = f.adjustFormulaRef(sheet, sheetN, cell.f, false, dir, num, offset); err != nil {
return err
}
}
if cell.F == nil {
return nil
}
if cell.F.Ref != "" && sheet == sheetN {
if cell.F.Ref, err = f.adjustCellRef(cell.F.Ref, dir, num, offset); err != nil {
return err
}
if si && cell.F.Si != nil {
cell.F.Si = intPtr(*cell.F.Si + 1)
}
}
if cell.F.Content != "" {
if cell.F.Content, err = f.adjustFormulaRef(sheet, sheetN, cell.F.Content, false, dir, num, offset); err != nil {
return err
}
}
return nil
}
// escapeSheetName enclose sheet name in single quotation marks if the giving
// worksheet name includes spaces or non-alphabetical characters.
func escapeSheetName(name string) string {
if strings.IndexFunc(name, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}) != -1 {
return "'" + strings.ReplaceAll(name, "'", "''") + "'"
}
return name
}
// adjustFormulaColumnName adjust column name in the formula reference.
func adjustFormulaColumnName(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
if name == "" || (!abs && keepRelative) {
return "", operand + name, abs, nil
}
col, err := ColumnNameToNumber(name)
if err != nil {
return "", operand, false, err
}
if dir == columns && col >= num {
if col += offset; col < 1 {
col = 1
}
colName, err := ColumnNumberToName(col)
return "", operand + colName, false, err
}
return "", operand + name, false, nil
}
// adjustFormulaRowNumber adjust row number in the formula reference.
func adjustFormulaRowNumber(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
if name == "" || (!abs && keepRelative) {
return "", operand + name, abs, nil
}
row, _ := strconv.Atoi(name)
if dir == rows && row >= num {
if row += offset; row < 1 {
row = 1
}
if row > TotalRows {
return "", operand + name, false, ErrMaxRows
}
return "", operand + strconv.Itoa(row), false, nil
}
return "", operand + name, false, nil
}
// adjustFormulaOperandRef adjust cell reference in the operand tokens for the formula.
func adjustFormulaOperandRef(row, col, operand string, abs, keepRelative bool, dir adjustDirection, num int, offset int) (string, string, string, bool, error) {
var err error
col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
if err != nil {
return row, col, operand, abs, err
}
row, operand, abs, err = adjustFormulaRowNumber(row, operand, abs, keepRelative, dir, num, offset)
return row, col, operand, abs, err
}
// adjustFormulaOperand adjust range operand tokens for the formula.
func (f *File) adjustFormulaOperand(sheet, sheetN string, keepRelative bool, token efp.Token, dir adjustDirection, num int, offset int) (string, error) {
var (
err error
abs bool
sheetName, col, row, operand string
cell = token.TValue
tokens = strings.Split(token.TValue, "!")
)
if len(tokens) == 2 { // have a worksheet
sheetName, cell = tokens[0], tokens[1]
operand = escapeSheetName(sheetName) + "!"
}
if sheetName == "" {
sheetName = sheetN
}
if sheet != sheetName {
return operand + cell, err
}
for _, r := range cell {
if r == '$' {
if col, operand, _, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset); err != nil {
return operand, err
}
abs = true
operand += string(r)
continue
}
if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') {
col += string(r)
continue
}
if '0' <= r && r <= '9' {
row += string(r)
col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
if err != nil {
return operand, err
}
continue
}
if row, col, operand, abs, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset); err != nil {
return operand, err
}
operand += string(r)
}
_, _, operand, _, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset)
return operand, err
}
// adjustFormulaRef returns adjusted formula by giving adjusting direction and
// the base number of column or row, and offset.
func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool, dir adjustDirection, num, offset int) (string, error) {
var (
val string
definedNames []string
ps = efp.ExcelParser()
)
for _, definedName := range f.GetDefinedName() {
if definedName.Scope == "Workbook" || definedName.Scope == sheet {
definedNames = append(definedNames, definedName.Name)
}
}
for _, token := range ps.Parse(formula) {
if token.TType == efp.TokenTypeUnknown {
val = formula
break
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
if inStrSlice(definedNames, token.TValue, true) != -1 {
val += token.TValue
continue
}
if strings.ContainsAny(token.TValue, "[]") {
val += token.TValue
continue
}
operand, err := f.adjustFormulaOperand(sheet, sheetN, keepRelative, token, dir, num, offset)
if err != nil {
return val, err
}
val += operand
continue
}
if paren := transformParenthesesToken(token); paren != "" {
val += transformParenthesesToken(token)
continue
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble)
continue
}
val += token.TValue
}
return val, nil
}
// transformParenthesesToken returns formula part with parentheses by given
// token.
func transformParenthesesToken(token efp.Token) string {
if isFunctionStartToken(token) || isBeginParenthesesToken(token) {
return token.TValue + string(efp.ParenOpen)
}
if isFunctionStopToken(token) || isEndParenthesesToken(token) {
return token.TValue + string(efp.ParenClose)
}
return ""
}
// adjustRangeSheetName returns replaced range reference by given source and
// target sheet name.
func adjustRangeSheetName(rng, source, target string) string {
source = escapeSheetName(source)
cellRefs := strings.Split(rng, ",")
for i, cellRef := range cellRefs {
rangeRefs := strings.Split(cellRef, ":")
for j, rangeRef := range rangeRefs {
parts := strings.Split(rangeRef, "!")
for k, part := range parts {
if strings.TrimPrefix(strings.TrimSuffix(part, "'"), "'") == source {
part = escapeSheetName(target)
}
parts[k] = part
}
rangeRefs[j] = strings.Join(parts, "!")
}
cellRefs[i] = strings.Join(rangeRefs, ":")
}
return strings.Join(cellRefs, ",")
}
// arrayFormulaOperandToken defines meta fields for transforming the array
// formula to the normal formula.
type arrayFormulaOperandToken struct {
operandTokenIndex, topLeftCol, topLeftRow, bottomRightCol, bottomRightRow int
sheetName, sourceCellRef, targetCellRef string
}
// setCoordinates convert each corner cell reference in the array formula cell
// range to the coordinate number.
func (af *arrayFormulaOperandToken) setCoordinates() error {
for i, ref := range strings.Split(af.sourceCellRef, ":") {
cellRef, col, row, err := parseRef(ref)
if err != nil {
return err
}
var c, r int
if col {
if cellRef.Row = TotalRows; i == 0 {
cellRef.Row = 1
}
}
if row {
if cellRef.Col = MaxColumns; i == 0 {
cellRef.Col = 1
}
}
if c, r = cellRef.Col, cellRef.Row; cellRef.Sheet != "" {
af.sheetName = cellRef.Sheet + "!"
}
if af.topLeftCol == 0 || c < af.topLeftCol {
af.topLeftCol = c
}
if af.topLeftRow == 0 || r < af.topLeftRow {
af.topLeftRow = r
}
if c > af.bottomRightCol {
af.bottomRightCol = c
}
if r > af.bottomRightRow {
af.bottomRightRow = r
}
}
return nil
}
// transformArrayFormula transforms an array formula to the normal formula by
// giving a formula tokens list and formula operand tokens list.
func transformArrayFormula(tokens []efp.Token, afs []arrayFormulaOperandToken) string {
var val string
for i, token := range tokens {
var skip bool
for _, af := range afs {
if af.operandTokenIndex == i {
val += af.sheetName + af.targetCellRef
skip = true
break
}
}
if skip {
continue
}
if paren := transformParenthesesToken(token); paren != "" {
val += transformParenthesesToken(token)
continue
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble)
continue
}
val += token.TValue
}
return val
}
// getArrayFormulaTokens returns parsed formula token and operand related token
// list for in array formula.
func getArrayFormulaTokens(sheet, formula string, definedNames []DefinedName) ([]efp.Token, []arrayFormulaOperandToken, error) {
var (
ps = efp.ExcelParser()
tokens = ps.Parse(formula)
arrayFormulaOperandTokens []arrayFormulaOperandToken
)
for i, token := range tokens {
if token.TSubType == efp.TokenSubTypeRange && token.TType == efp.TokenTypeOperand {
tokenVal := token.TValue
for _, definedName := range definedNames {
if (definedName.Scope == "Workbook" || definedName.Scope == sheet) && definedName.Name == tokenVal {
tokenVal = definedName.RefersTo
}
}
if len(strings.Split(tokenVal, ":")) > 1 {
arrayFormulaOperandToken := arrayFormulaOperandToken{
operandTokenIndex: i,
sourceCellRef: tokenVal,
}
if err := arrayFormulaOperandToken.setCoordinates(); err != nil {
return tokens, arrayFormulaOperandTokens, err
}
arrayFormulaOperandTokens = append(arrayFormulaOperandTokens, arrayFormulaOperandToken)
}
}
}
return tokens, arrayFormulaOperandTokens, nil
}
// adjustHyperlinks provides a function to update hyperlinks when inserting or
// deleting rows or columns.
func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
// short path
if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 {
return
}
// order is important
if offset < 0 {
for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- {
linkData := ws.Hyperlinks.Hyperlink[i]
colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
f.deleteSheetRelationships(sheet, linkData.RID)
if len(ws.Hyperlinks.Hyperlink) > 1 {
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i],
ws.Hyperlinks.Hyperlink[i+1:]...)
} else {
ws.Hyperlinks = nil
}
}
}
}
if ws.Hyperlinks == nil {
return
}
for i := range ws.Hyperlinks.Hyperlink {
link := &ws.Hyperlinks.Hyperlink[i] // get reference
link.Ref, _ = f.adjustFormulaRef(sheet, sheet, link.Ref, false, dir, num, offset)
}
}
// adjustTable provides a function to update the table when inserting or
// deleting rows or columns.
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
return nil
}
for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
tbl := ws.TableParts.TableParts[idx]
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
tableXML := strings.ReplaceAll(target, "..", "xl")
content, ok := f.Pkg.Load(tableXML)
if !ok {
continue
}
t := xlsxTable{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return err
}
coordinates, err := rangeRefToCoordinates(t.Ref)
if err != nil {
return err
}
// Remove the table when deleting the header row of the table
if dir == rows && num == coordinates[0] && offset == -1 {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
ws.TableParts.Count = len(ws.TableParts.TableParts)
idx--
continue
}
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if y2-y1 < 1 || x2-x1 < 0 {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
ws.TableParts.Count = len(ws.TableParts.TableParts)
idx--
continue
}
t.Ref, _ = coordinatesToRangeRef([]int{x1, y1, x2, y2})
if t.AutoFilter != nil {
t.AutoFilter.Ref = t.Ref
}
_ = f.setTableColumns(sheet, true, x1, y1, x2, &t)
// Currently doesn't support query table
t.TableType, t.TotalsRowCount, t.ConnectionID = "", 0, 0
table, _ := xml.Marshal(t)
f.saveFileList(tableXML, table)
}
return nil
}
// adjustAutoFilter provides a function to update the auto filter when
// inserting or deleting rows or columns.
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.AutoFilter == nil {
return nil
}
coordinates, err := rangeRefToCoordinates(ws.AutoFilter.Ref)
if err != nil {
return err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
ws.AutoFilter = nil
for rowIdx := range ws.SheetData.Row {
rowData := &ws.SheetData.Row[rowIdx]
if rowData.R > y1 && rowData.R <= y2 {
rowData.Hidden = false
}
}
return err
}
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
ws.AutoFilter.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2})
return err
}
// adjustAutoFilterHelper provides a function for adjusting auto filter to
// compare and calculate cell reference by the giving adjusting direction,
// operation reference and offset.
func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
if dir == rows {
if coordinates[1] >= num {
coordinates[1] += offset
}
if coordinates[3] >= num {
coordinates[3] += offset
}
return coordinates
}
if coordinates[0] >= num {
coordinates[0] += offset
}
if coordinates[2] >= num {
coordinates[2] += offset
}
return coordinates
}
// adjustMergeCells provides a function to update merged cells when inserting
// or deleting rows or columns.
func (f *File) adjustMergeCells(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.MergeCells == nil {
return nil
}
for i := 0; i < len(ws.MergeCells.Cells); i++ {
mergedCells := ws.MergeCells.Cells[i]
mergedCellsRef := mergedCells.Ref
if !strings.Contains(mergedCellsRef, ":") {
mergedCellsRef += ":" + mergedCellsRef
}
coordinates, err := rangeRefToCoordinates(mergedCellsRef)
if err != nil {
return err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if dir == rows {
if y1 == num && y2 == num && offset < 0 {
f.deleteMergeCell(ws, i)
i--
continue
}
y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset)
} else {
if x1 == num && x2 == num && offset < 0 {
f.deleteMergeCell(ws, i)
i--
continue
}
x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset)
}
if x1 == x2 && y1 == y2 {
f.deleteMergeCell(ws, i)
i--
continue
}
mergedCells.rect = []int{x1, y1, x2, y2}
if mergedCells.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
return err
}
}
return nil
}
// adjustMergeCellsHelper provides a function for adjusting merge cells to
// compare and calculate cell reference by the given pivot, operation reference
// and offset.
func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) {
if p2 < p1 {
p1, p2 = p2, p1
}
if offset >= 0 {
if num <= p1 {
p1 += offset
p2 += offset
} else if num <= p2 {
p2 += offset
}
return p1, p2
}
if num < p1 || (num == p1 && num == p2) {
p1 += offset
p2 += offset
} else if num <= p2 {
p2 += offset
}
return p1, p2
}
// deleteMergeCell provides a function to delete merged cell by given index.
func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
if idx < 0 {
return
}
if len(ws.MergeCells.Cells) > idx {
ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...)
ws.MergeCells.Count = len(ws.MergeCells.Cells)
}
}
// adjustCellName returns updated cell name by giving column/row number and
// offset on inserting or deleting rows or columns.
func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string, error) {
if dir == rows {
if rn := r + offset; rn > 0 {
return CoordinatesToCellName(c, rn)
}
}
return CoordinatesToCellName(c+offset, r)
}
// adjustCalcChain provides a function to update the calculation chain when
// inserting or deleting rows or columns.
func (f *File) adjustCalcChain(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if f.CalcChain == nil {
return nil
}
// If sheet ID is omitted, it is assumed to be the same as the i value of
// the previous cell.
var prevSheetID int
for i := 0; f.CalcChain != nil && i < len(f.CalcChain.C); i++ {
c := f.CalcChain.C[i]
if c.I == 0 {
c.I = prevSheetID
}
prevSheetID = c.I
if c.I != sheetID {
continue
}
colNum, rowNum, err := CellNameToCoordinates(c.R)
if err != nil {
return err
}
if dir == rows && num <= rowNum {
if num == rowNum && offset == -1 {
_ = f.deleteCalcChain(c.I, c.R)
i--
continue
}
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
}
if dir == columns && num <= colNum {
if num == colNum && offset == -1 {
_ = f.deleteCalcChain(c.I, c.R)
i--
continue
}
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
}
}
return nil
}
// adjustVolatileDepsTopic updates the volatile dependencies topic when
// inserting or deleting rows or columns.
func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection, indexes []int) (int, error) {
num, offset, i1, i2, i3, i4 := indexes[0], indexes[1], indexes[2], indexes[3], indexes[4], indexes[5]
colNum, rowNum, err := CellNameToCoordinates(cell)
if err != nil {
return i4, err
}
if dir == rows && num <= rowNum {
if num == rowNum && offset == -1 {
vt.deleteVolTopicRef(i1, i2, i3, i4)
i4--
return i4, err
}
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
}
if dir == columns && num <= colNum {
if num == colNum && offset == -1 {
vt.deleteVolTopicRef(i1, i2, i3, i4)
i4--
return i4, err
}
if name, _ := adjustCellName(cell, dir, colNum, rowNum, offset); name != "" {
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
}
}
return i4, err
}
// adjustVolatileDeps updates the volatile dependencies when inserting or
// deleting rows or columns.
func (f *File) adjustVolatileDeps(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
volTypes, err := f.volatileDepsReader()
if err != nil || volTypes == nil {
return err
}
for i1 := 0; i1 < len(volTypes.VolType); i1++ {
for i2 := 0; i2 < len(volTypes.VolType[i1].Main); i2++ {
for i3 := 0; i3 < len(volTypes.VolType[i1].Main[i2].Tp); i3++ {
for i4 := 0; i4 < len(volTypes.VolType[i1].Main[i2].Tp[i3].Tr); i4++ {
ref := volTypes.VolType[i1].Main[i2].Tp[i3].Tr[i4]
if ref.S != sheetID {
continue
}
if i4, err = volTypes.adjustVolatileDepsTopic(ref.R, dir, []int{num, offset, i1, i2, i3, i4}); err != nil {
return err
}
}
}
}
}
return nil
}
// adjustConditionalFormats updates the cell reference of the worksheet
// conditional formatting when inserting or deleting rows or columns.
func (f *File) adjustConditionalFormats(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
for i := 0; i < len(ws.ConditionalFormatting); i++ {
cf := ws.ConditionalFormatting[i]
if cf == nil {
continue
}
ref, err := f.adjustCellRef(cf.SQRef, dir, num, offset)
if err != nil {
return err
}
if ref == "" {
ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i],
ws.ConditionalFormatting[i+1:]...)
i--
continue
}
ws.ConditionalFormatting[i].SQRef = ref
}
return nil
}
// adjustDataValidations updates the range of data validations for the worksheet
// when inserting or deleting rows or columns.
func (f *File) adjustDataValidations(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
for _, sheetN := range f.GetSheetList() {
worksheet, err := f.workSheetReader(sheetN)
if err != nil {
if err.Error() == newNotWorksheetError(sheetN).Error() {
continue
}
return err
}
if worksheet.DataValidations == nil {
continue
}
for i := 0; i < len(worksheet.DataValidations.DataValidation); i++ {
dv := worksheet.DataValidations.DataValidation[i]
if dv == nil {
continue
}
if sheet == sheetN {
ref, err := f.adjustCellRef(dv.Sqref, dir, num, offset)
if err != nil {
return err
}
if ref == "" {
worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation[:i],
worksheet.DataValidations.DataValidation[i+1:]...)
i--
continue
}
worksheet.DataValidations.DataValidation[i].Sqref = ref
}
if worksheet.DataValidations.DataValidation[i].Formula1.isFormula() {
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula1.Content)
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
return err
}
worksheet.DataValidations.DataValidation[i].Formula1 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)}
}
if worksheet.DataValidations.DataValidation[i].Formula2.isFormula() {
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula2.Content)
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
return err
}
worksheet.DataValidations.DataValidation[i].Formula2 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)}
}
}
if worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation); worksheet.DataValidations.Count == 0 {
worksheet.DataValidations = nil
}
}
return nil
}
// adjustDrawings updates the starting anchor of the two cell anchor pictures
// and charts object when inserting or deleting rows or columns.
func (from *xlsxFrom) adjustDrawings(dir adjustDirection, num, offset int, editAs string) (bool, error) {
var ok bool
if dir == columns && from.Col+1 >= num && from.Col+offset >= 0 {
if from.Col+offset >= MaxColumns {
return false, ErrColumnNumber
}
from.Col += offset
ok = editAs == "oneCell"
}
if dir == rows && from.Row+1 >= num && from.Row+offset >= 0 {
if from.Row+offset >= TotalRows {
return false, ErrMaxRows
}
from.Row += offset
ok = editAs == "oneCell"
}
return ok, nil
}
// adjustDrawings updates the ending anchor of the two cell anchor pictures
// and charts object when inserting or deleting rows or columns.
func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, ok bool) error {
if dir == columns && to.Col+1 >= num && to.Col+offset >= 0 && ok {
if to.Col+offset >= MaxColumns {
return ErrColumnNumber
}
to.Col += offset
}
if dir == rows && to.Row+1 >= num && to.Row+offset >= 0 && ok {
if to.Row+offset >= TotalRows {
return ErrMaxRows
}
to.Row += offset
}
return nil
}
// adjustDrawings updates the two cell anchor pictures and charts object when
// inserting or deleting rows or columns.
func (a *xdrCellAnchor) adjustDrawings(dir adjustDirection, num, offset int) error {
editAs := a.EditAs
if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" {
return nil
}
ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
if err != nil {
return err
}
if a.To != nil {
return a.To.adjustDrawings(dir, num, offset, ok || editAs == "")
}
return err
}
// adjustDrawings updates the existing two cell anchor pictures and charts
// object when inserting or deleting rows or columns.
func (a *xlsxCellAnchorPos) adjustDrawings(dir adjustDirection, num, offset int, editAs string) error {
if (a.From == nil && (a.To == nil || a.Ext == nil)) || editAs == "absolute" {
return nil
}
ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
if err != nil {
return err
}
if a.To != nil {
return a.To.adjustDrawings(dir, num, offset, ok || editAs == "")
}
return err
}
// adjustDrawings updates the pictures and charts object when inserting or
// deleting rows or columns.
func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) error {
if ws.Drawing == nil {
return nil
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
var (
err error
wsDr *xlsxWsDr
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return err
}
anchorCb := func(a *xdrCellAnchor) error {
if a.GraphicFrame == "" {
return a.adjustDrawings(dir, num, offset)
}
deCellAnchor := decodeCellAnchor{}
deCellAnchorPos := decodeCellAnchorPos{}
_ = f.xmlNewDecoder(strings.NewReader("" + a.GraphicFrame + "")).Decode(&deCellAnchor)
_ = f.xmlNewDecoder(strings.NewReader("" + a.GraphicFrame + "")).Decode(&deCellAnchorPos)
xlsxCellAnchorPos := xlsxCellAnchorPos(deCellAnchorPos)
for i := 0; i < len(xlsxCellAnchorPos.AlternateContent); i++ {
xlsxCellAnchorPos.AlternateContent[i].XMLNSMC = SourceRelationshipCompatibility.Value
}
if deCellAnchor.From != nil {
xlsxCellAnchorPos.From = &xlsxFrom{
Col: deCellAnchor.From.Col, ColOff: deCellAnchor.From.ColOff,
Row: deCellAnchor.From.Row, RowOff: deCellAnchor.From.RowOff,
}
}
if deCellAnchor.To != nil {
xlsxCellAnchorPos.To = &xlsxTo{
Col: deCellAnchor.To.Col, ColOff: deCellAnchor.To.ColOff,
Row: deCellAnchor.To.Row, RowOff: deCellAnchor.To.RowOff,
}
}
if err = xlsxCellAnchorPos.adjustDrawings(dir, num, offset, a.EditAs); err != nil {
return err
}
cellAnchor, _ := xml.Marshal(xlsxCellAnchorPos)
a.GraphicFrame = strings.TrimSuffix(strings.TrimPrefix(string(cellAnchor), ""), "")
return err
}
for _, anchor := range wsDr.TwoCellAnchor {
if err = anchorCb(anchor); err != nil {
return err
}
}
for _, anchor := range wsDr.OneCellAnchor {
if err = anchorCb(anchor); err != nil {
return err
}
}
return nil
}
// adjustDefinedNames updates the cell reference of the defined names when
// inserting or deleting rows or columns.
func (f *File) adjustDefinedNames(sheet string, dir adjustDirection, num, offset int) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.DefinedNames != nil {
for i := 0; i < len(wb.DefinedNames.DefinedName); i++ {
data := wb.DefinedNames.DefinedName[i].Data
if data, err = f.adjustFormulaRef(sheet, "", data, true, dir, num, offset); err == nil {
wb.DefinedNames.DefinedName[i].Data = data
}
}
}
return nil
}
================================================
FILE: adjust_test.go
================================================
package excelize
import (
"encoding/xml"
"fmt"
"path/filepath"
"strings"
"testing"
_ "image/jpeg"
"github.com/stretchr/testify/assert"
)
func TestAdjustMergeCells(t *testing.T) {
f := NewFile()
// Test adjustAutoFilter with illegal cell reference
assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A:B1",
},
},
},
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:B",
},
},
},
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:B1",
},
},
},
}, "Sheet1", rows, 1, -1, 1))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:A2",
},
},
},
}, "Sheet1", columns, 1, -1, 1))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2",
},
},
},
}, "Sheet1", columns, 1, -1, 1))
// Test adjust merge cells
var cases []struct {
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}
// Test adjust merged cell when insert rows and columns
cases = []struct {
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
label: "insert row on ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 2,
offset: 1,
expect: "A3:B4",
expectRect: []int{1, 3, 2, 4},
},
{
label: "insert row on bottom of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 3,
offset: 1,
expect: "A2:B4",
expectRect: []int{1, 2, 2, 4},
},
{
label: "insert column on the left",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 1,
offset: 1,
expect: "B2:C3",
expectRect: []int{2, 2, 3, 3},
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, 1, 1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label)
}
// Test adjust merged cells when delete rows and columns
cases = []struct {
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
label: "delete row on top of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 2,
offset: -1,
expect: "A2:B2",
expectRect: []int{1, 2, 2, 2},
},
{
label: "delete row on bottom of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 3,
offset: -1,
expect: "A2:B2",
expectRect: []int{1, 2, 2, 2},
},
{
label: "delete column on the ref left",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 1,
offset: -1,
expect: "A2:A3",
expectRect: []int{1, 2, 1, 3},
},
{
label: "delete column on the ref right",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 2,
offset: -1,
expect: "A2:A3",
expectRect: []int{1, 2, 1, 3},
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
}
// Test delete one row or column
cases = []struct {
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
label: "delete one row ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:B1",
rect: []int{1, 1, 2, 1},
},
},
},
},
dir: rows,
num: 1,
offset: -1,
},
{
label: "delete one column ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:A2",
rect: []int{1, 1, 1, 2},
},
},
},
},
dir: columns,
num: 1,
offset: -1,
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1))
assert.Len(t, c.ws.MergeCells.Cells, 0, c.label)
}
f = NewFile()
p1, p2 := f.adjustMergeCellsHelper(2, 1, 0, 0)
assert.Equal(t, 1, p1)
assert.Equal(t, 2, p2)
f.deleteMergeCell(nil, -1)
}
func TestAdjustAutoFilter(t *testing.T) {
f := NewFile()
assert.NoError(t, f.adjustAutoFilter(&xlsxWorksheet{
SheetData: xlsxSheetData{
Row: []xlsxRow{{Hidden: true, R: 2}},
},
AutoFilter: &xlsxAutoFilter{
Ref: "A1:A3",
},
}, "Sheet1", rows, 1, -1, 1))
// Test adjustAutoFilter with illegal cell reference
assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A:B1",
},
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A1:B",
},
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
}
func TestAdjustTable(t *testing.T) {
f, sheetName := NewFile(), "Sheet1"
for idx, reference := range []string{"B2:C3", "E3:F5", "H5:H8", "J5:K9"} {
assert.NoError(t, f.AddTable(sheetName, &Table{
Range: reference,
Name: fmt.Sprintf("table%d", idx),
StyleName: "TableStyleMedium2",
ShowFirstColumn: true,
ShowLastColumn: true,
ShowRowStripes: boolPtr(false),
ShowColumnStripes: true,
}))
}
assert.NoError(t, f.RemoveRow(sheetName, 2))
assert.NoError(t, f.RemoveRow(sheetName, 3))
assert.NoError(t, f.RemoveRow(sheetName, 3))
assert.NoError(t, f.RemoveCol(sheetName, "H"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx")))
f = NewFile()
assert.NoError(t, f.AddTable(sheetName, &Table{Range: "A1:D5"}))
// Test adjust table with non-table part
f.Pkg.Delete("xl/tables/table1.xml")
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with unsupported charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.RemoveRow(sheetName, 1), "XML syntax error on line 1: invalid UTF-8")
// Test adjust table with invalid table range reference
f.Pkg.Store("xl/tables/table1.xml", []byte(``))
assert.Equal(t, ErrParameterInvalid, f.RemoveRow(sheetName, 1))
}
func TestAdjustHelper(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
})
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
})
// Test adjustHelper with illegal cell reference
assert.Equal(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
// Test adjustHelper on not exists worksheet
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN does not exist")
}
func TestAdjustCalcChain(t *testing.T) {
f := NewFile()
f.CalcChain = &xlsxCalcChain{
C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}, {R: "A1", I: 1}},
}
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
f.CalcChain = &xlsxCalcChain{
C: []xlsxCalcChainC{{R: "B2", I: 1}, {R: "B3"}, {R: "A1"}},
}
assert.NoError(t, f.RemoveRow("Sheet1", 3))
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}}}
f.CalcChain.C[1].R = "invalid coordinates"
assert.Equal(t, f.InsertCols("Sheet1", "A", 1), newCellNameToCoordinatesError("invalid coordinates", newInvalidCellNameError("invalid coordinates")))
f.CalcChain = nil
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
}
func TestAdjustCols(t *testing.T) {
sheetName := "Sheet1"
preset := func() (*File, error) {
f := NewFile()
if err := f.SetColWidth(sheetName, "J", "T", 5); err != nil {
return f, err
}
if err := f.SetSheetRow(sheetName, "J1", &[]string{"J1", "K1", "L1", "M1", "N1", "O1", "P1", "Q1", "R1", "S1", "T1"}); err != nil {
return f, err
}
return f, nil
}
baseTbl := []string{"B", "J", "O", "O", "O", "U", "V"}
insertTbl := []int{2, 2, 2, 5, 6, 2, 2}
expectedTbl := []map[string]float64{
{"J": defaultColWidth, "K": defaultColWidth, "U": 5, "V": 5, "W": defaultColWidth},
{"J": defaultColWidth, "K": defaultColWidth, "U": 5, "V": 5, "W": defaultColWidth},
{"O": 5, "P": 5, "U": 5, "V": 5, "W": defaultColWidth},
{"O": 5, "S": 5, "X": 5, "Y": 5, "Z": defaultColWidth},
{"O": 5, "S": 5, "Y": 5, "X": 5, "AA": defaultColWidth},
{"U": 5, "V": 5, "W": defaultColWidth},
{"U": defaultColWidth, "V": defaultColWidth, "W": defaultColWidth},
}
for idx, columnName := range baseTbl {
f, err := preset()
assert.NoError(t, err)
assert.NoError(t, f.InsertCols(sheetName, columnName, insertTbl[idx]))
for column, expected := range expectedTbl[idx] {
width, err := f.GetColWidth(sheetName, column)
assert.NoError(t, err)
assert.Equal(t, expected, width, column)
}
assert.NoError(t, f.Close())
}
baseTbl = []string{"B", "J", "O", "T"}
expectedTbl = []map[string]float64{
{"H": defaultColWidth, "I": 5, "S": 5, "T": defaultColWidth},
{"I": defaultColWidth, "J": 5, "S": 5, "T": defaultColWidth},
{"I": defaultColWidth, "O": 5, "S": 5, "T": defaultColWidth},
{"R": 5, "S": 5, "T": defaultColWidth, "U": defaultColWidth},
}
for idx, columnName := range baseTbl {
f, err := preset()
assert.NoError(t, err)
assert.NoError(t, f.RemoveCol(sheetName, columnName))
for column, expected := range expectedTbl[idx] {
width, err := f.GetColWidth(sheetName, column)
assert.NoError(t, err)
assert.Equal(t, expected, width, column)
}
assert.NoError(t, f.Close())
}
f, err := preset()
assert.NoError(t, err)
assert.NoError(t, f.SetColWidth(sheetName, "I", "I", 8))
for i := 0; i <= 12; i++ {
assert.NoError(t, f.RemoveCol(sheetName, "I"))
}
for c := 9; c <= 21; c++ {
columnName, err := ColumnNumberToName(c)
assert.NoError(t, err)
width, err := f.GetColWidth(sheetName, columnName)
assert.NoError(t, err)
assert.Equal(t, defaultColWidth, width, columnName)
}
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Cols = nil
assert.NoError(t, f.RemoveCol(sheetName, "A"))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.SetColWidth("Sheet1", "XFB", "XFC", 12))
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Min)
assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Max)
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
assert.Nil(t, ws.(*xlsxWorksheet).Cols)
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "(1-0.5)/2"))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
formula, err := f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "(1-0.5)/2", formula)
assert.NoError(t, f.Close())
}
func TestAdjustColDimensions(t *testing.T) {
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1"))
assert.Equal(t, ErrColumnNumber, f.adjustColDimensions("Sheet1", ws, 1, MaxColumns))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet2.xml")
f.Pkg.Store("xl/worksheets/sheet2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.adjustColDimensions("Sheet2", ws, 2, 1), "XML syntax error on line 1: invalid UTF-8")
}
func TestAdjustRowDimensions(t *testing.T) {
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1"))
assert.Equal(t, ErrMaxRows, f.adjustRowDimensions("Sheet1", ws, 1, TotalRows))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet2.xml")
f.Pkg.Store("xl/worksheets/sheet2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.adjustRowDimensions("Sheet1", ws, 2, 1), "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
ws, err = f.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("Sheet2!A%d", TotalRows)))
assert.Equal(t, ErrMaxRows, f.adjustRowDimensions("Sheet2", ws, 1, TotalRows))
}
func TestAdjustHyperlinks(t *testing.T) {
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "C3", "A1+B1"))
f.adjustHyperlinks(ws, "Sheet1", rows, 3, -1)
// Test adjust hyperlinks location with positive offset
assert.NoError(t, f.SetCellHyperLink("Sheet1", "F5", "Sheet1!A1", "Location"))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
link, target, err := f.GetCellHyperLink("Sheet1", "F6")
assert.NoError(t, err)
assert.True(t, link)
assert.Equal(t, target, "Sheet1!A1")
// Test adjust hyperlinks location with negative offset
assert.NoError(t, f.RemoveRow("Sheet1", 1))
link, target, err = f.GetCellHyperLink("Sheet1", "F5")
assert.NoError(t, err)
assert.True(t, link)
assert.Equal(t, target, "Sheet1!A1")
// Test adjust hyperlinks location on remove row
assert.NoError(t, f.RemoveRow("Sheet1", 5))
link, target, err = f.GetCellHyperLink("Sheet1", "F5")
assert.NoError(t, err)
assert.False(t, link)
assert.Empty(t, target)
// Test adjust hyperlinks location on remove column
assert.NoError(t, f.SetCellHyperLink("Sheet1", "F5", "Sheet1!A1", "Location"))
assert.NoError(t, f.RemoveCol("Sheet1", "F"))
link, target, err = f.GetCellHyperLink("Sheet1", "F5")
assert.NoError(t, err)
assert.False(t, link)
assert.Empty(t, target)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustHyperlinks.xlsx")))
assert.NoError(t, f.Close())
}
func TestAdjustFormula(t *testing.T) {
f := NewFile()
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10))
assert.NoError(t, f.InsertCols("Sheet1", "B", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
for cell, expected := range map[string]string{"D2": "A2+C2", "D3": "A3+C3", "D11": "A11+C11"} {
formula, err := f.GetCellFormula("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, formula)
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx")))
assert.NoError(t, f.Close())
assert.NoError(t, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{}, rows, 0, 0, false))
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "-"}}, rows, 0, 0, false))
assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "XFD1:XFD1"}}, columns, 0, 1, false))
_, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", false, columns, 0, 1)
assert.Equal(t, ErrColumnNumber, err)
_, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", false, columns, 0, 1)
assert.Equal(t, ErrColumnNumber, err)
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "XFD1"))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("A%d", TotalRows)))
assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1))
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(1048576:1:2)"))
assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1))
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(XFD:A:B)"))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(A:B:XFD)"))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
// Test adjust formula with defined name in formula text
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$B$2",
}))
assert.NoError(t, f.SetCellFormula("Sheet1", "B2", "Amount+B3"))
assert.NoError(t, f.RemoveRow("Sheet1", 1))
formula, err := f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "Amount+B2", formula)
// Test adjust formula with array formula
f = NewFile()
formulaType, reference := STCellFormulaTypeArray, "A3:A3"
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType}))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
formula, err = f.GetCellFormula("Sheet1", "A4")
assert.NoError(t, err)
assert.Equal(t, "A2:A3", formula)
// Test adjust formula on duplicate row with array formula
f = NewFile()
formulaType, reference = STCellFormulaTypeArray, "A3"
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType}))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
formula, err = f.GetCellFormula("Sheet1", "A4")
assert.NoError(t, err)
assert.Equal(t, "A2:A3", formula)
// Test adjust formula on duplicate row with relative and absolute cell references
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "A$10+$A11&\" \""))
assert.NoError(t, f.DuplicateRowTo("Sheet1", 10, 2))
formula, err = f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "A$2+$A3&\" \"", formula)
t.Run("for_cells_affected_directly", func(t *testing.T) {
// Test insert row in middle of range with relative and absolute cell references
f := NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "$A1+A$2"))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err := f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "$A1+A$3", formula)
assert.NoError(t, f.RemoveRow("Sheet1", 2))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "$A1+A$2", formula)
// Test insert column in middle of range
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "B1+C1"))
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "B1+D1", formula)
assert.NoError(t, f.RemoveCol("Sheet1", "C"))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "B1+C1", formula)
// Test insert row and column in a rectangular range
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "D4+D5+E4+E5"))
assert.NoError(t, f.InsertCols("Sheet1", "E", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "D4+D6+F4+F6", formula)
// Test insert row in middle of range
f = NewFile()
formulaType, reference := STCellFormulaTypeArray, "B1:B1"
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType}))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "A1:A3", formula)
assert.NoError(t, f.RemoveRow("Sheet1", 2))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "A1:A2", formula)
// Test insert column in middle of range
f = NewFile()
formulaType, reference = STCellFormulaTypeArray, "A1:A1"
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "B1:C1", FormulaOpts{Ref: &reference, Type: &formulaType}))
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "B1:D1", formula)
assert.NoError(t, f.RemoveCol("Sheet1", "C"))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "B1:C1", formula)
// Test insert row and column in a rectangular range
f = NewFile()
formulaType, reference = STCellFormulaTypeArray, "A1:A1"
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "D4:E5", FormulaOpts{Ref: &reference, Type: &formulaType}))
assert.NoError(t, f.InsertCols("Sheet1", "E", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
formula, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "D4:F6", formula)
})
t.Run("for_cells_affected_indirectly", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A3+A4"))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err := f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "A4+A5", formula)
assert.NoError(t, f.RemoveRow("Sheet1", 2))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "A3+A4", formula)
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "D3+D4"))
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "E3+E4", formula)
assert.NoError(t, f.RemoveCol("Sheet1", "C"))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "D3+D4", formula)
})
t.Run("for_entire_cols_rows_reference", func(t *testing.T) {
f := NewFile()
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(A2:A3:A4,,Table1[])"))
assert.NoError(t, f.InsertRows("Sheet1", 3, 1))
formula, err := f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(A2:A4:A5,,Table1[])", formula)
// Test adjust formula on insert at the top of the range
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(A3:A5:A6,,Table1[])", formula)
f = NewFile()
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM('Sheet 1'!A2,A3)"))
assert.NoError(t, f.InsertRows("Sheet1", 3, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM('Sheet 1'!A2,A4)", formula)
// Test adjust formula on insert row at the top of the range
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM('Sheet 1'!A2,A5)", formula)
f = NewFile()
// Test adjust formula on insert col in the middle of the range
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C3:D3)"))
assert.NoError(t, f.InsertCols("Sheet1", "D", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(C3:E3)", formula)
// Test adjust formula on insert at the top of the range
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(D3:F3)", formula)
f = NewFile()
// Test adjust formula on insert column in the middle of the range
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C3,D3)"))
assert.NoError(t, f.InsertCols("Sheet1", "D", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(C3,E3)", formula)
// Test adjust formula on insert column at the top of the range
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(D3,F3)", formula)
f = NewFile()
// Test adjust formula on insert row in the middle of the range (range of whole row)
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(2:3)"))
assert.NoError(t, f.InsertRows("Sheet1", 3, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(2:4)", formula)
// Test adjust formula on insert row at the top of the range (range of whole row)
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(3:5)", formula)
f = NewFile()
// Test adjust formula on insert row in the middle of the range (range of whole column)
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(C:D)"))
assert.NoError(t, f.InsertCols("Sheet1", "D", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(C:E)", formula)
// Test adjust formula on insert row at the top of the range (range of whole column)
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(D:F)", formula)
})
t.Run("for_all_worksheet_cells_with_rows_insert", func(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
// Tests formulas referencing Sheet2 should update but those referencing the original sheet should not
tbl := [][]string{
{"B1", "Sheet2!A1+Sheet2!A2", "Sheet2!A1+Sheet2!A3", "Sheet2!A2+Sheet2!A4"},
{"C1", "A1+A2", "A1+A2", "A1+A2"},
{"D1", "Sheet2!B1:B2", "Sheet2!B1:B3", "Sheet2!B2:B4"},
{"E1", "B1:B2", "B1:B2", "B1:B2"},
{"F1", "SUM(Sheet2!C1:C2)", "SUM(Sheet2!C1:C3)", "SUM(Sheet2!C2:C4)"},
{"G1", "SUM(C1:C2)", "SUM(C1:C2)", "SUM(C1:C2)"},
{"H1", "SUM(Sheet2!D1,Sheet2!D2)", "SUM(Sheet2!D1,Sheet2!D3)", "SUM(Sheet2!D2,Sheet2!D4)"},
{"I1", "SUM(D1,D2)", "SUM(D1,D2)", "SUM(D1,D2)"},
}
for _, preset := range tbl {
assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1]))
}
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.InsertRows("Sheet2", 2, 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[2], formula)
}
// Test adjust formula on insert row in the top of the range
assert.NoError(t, f.InsertRows("Sheet2", 1, 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[3], formula)
}
})
t.Run("for_all_worksheet_cells_with_cols_insert", func(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
tbl := [][]string{
{"A1", "Sheet2!A1+Sheet2!B1", "Sheet2!A1+Sheet2!C1", "Sheet2!B1+Sheet2!D1"},
{"A2", "A1+B1", "A1+B1", "A1+B1"},
{"A3", "Sheet2!A2:B2", "Sheet2!A2:C2", "Sheet2!B2:D2"},
{"A4", "A2:B2", "A2:B2", "A2:B2"},
{"A5", "SUM(Sheet2!A3:B3)", "SUM(Sheet2!A3:C3)", "SUM(Sheet2!B3:D3)"},
{"A6", "SUM(A3:B3)", "SUM(A3:B3)", "SUM(A3:B3)"},
{"A7", "SUM(Sheet2!A4,Sheet2!B4)", "SUM(Sheet2!A4,Sheet2!C4)", "SUM(Sheet2!B4,Sheet2!D4)"},
{"A8", "SUM(A4,B4)", "SUM(A4,B4)", "SUM(A4,B4)"},
}
for _, preset := range tbl {
assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1]))
}
// Test adjust formula on insert column in the middle of the range
assert.NoError(t, f.InsertCols("Sheet2", "B", 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[2], formula)
}
// Test adjust formula on insert column in the top of the range
assert.NoError(t, f.InsertCols("Sheet2", "A", 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[3], formula)
}
})
t.Run("for_cross_sheet_ref_with_rows_insert)", func(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
// Tests formulas referencing Sheet2 should update but those referencing
// the original sheet or Sheet 3 should not update
tbl := [][]string{
{"B1", "Sheet2!A1+Sheet2!A2+Sheet1!A3+Sheet1!A4", "Sheet2!A1+Sheet2!A3+Sheet1!A3+Sheet1!A4", "Sheet2!A2+Sheet2!A4+Sheet1!A3+Sheet1!A4"},
{"C1", "Sheet2!B1+Sheet2!B2+B3+B4", "Sheet2!B1+Sheet2!B3+B3+B4", "Sheet2!B2+Sheet2!B4+B3+B4"},
{"D1", "Sheet2!C1+Sheet2!C2+Sheet3!A3+Sheet3!A4", "Sheet2!C1+Sheet2!C3+Sheet3!A3+Sheet3!A4", "Sheet2!C2+Sheet2!C4+Sheet3!A3+Sheet3!A4"},
{"E1", "SUM(Sheet2!D1:D2,Sheet1!A3:A4)", "SUM(Sheet2!D1:D3,Sheet1!A3:A4)", "SUM(Sheet2!D2:D4,Sheet1!A3:A4)"},
{"F1", "SUM(Sheet2!E1:E2,A3:A4)", "SUM(Sheet2!E1:E3,A3:A4)", "SUM(Sheet2!E2:E4,A3:A4)"},
{"G1", "SUM(Sheet2!F1:F2,Sheet3!A3:A4)", "SUM(Sheet2!F1:F3,Sheet3!A3:A4)", "SUM(Sheet2!F2:F4,Sheet3!A3:A4)"},
}
for _, preset := range tbl {
assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1]))
}
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.InsertRows("Sheet2", 2, 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[2], formula)
}
// Test adjust formula on insert row in the top of the range
assert.NoError(t, f.InsertRows("Sheet2", 1, 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[3], formula)
}
})
t.Run("for_cross_sheet_ref_with_cols_insert)", func(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
// Tests formulas referencing Sheet2 should update but those referencing
// the original sheet or Sheet 3 should not update
tbl := [][]string{
{"A1", "Sheet2!A1+Sheet2!B1+Sheet1!C1+Sheet1!D1", "Sheet2!A1+Sheet2!C1+Sheet1!C1+Sheet1!D1", "Sheet2!B1+Sheet2!D1+Sheet1!C1+Sheet1!D1"},
{"A2", "Sheet2!A2+Sheet2!B2+C2+D2", "Sheet2!A2+Sheet2!C2+C2+D2", "Sheet2!B2+Sheet2!D2+C2+D2"},
{"A3", "Sheet2!A3+Sheet2!B3+Sheet3!C3+Sheet3!D3", "Sheet2!A3+Sheet2!C3+Sheet3!C3+Sheet3!D3", "Sheet2!B3+Sheet2!D3+Sheet3!C3+Sheet3!D3"},
{"A4", "SUM(Sheet2!A4:B4,Sheet1!C4:D4)", "SUM(Sheet2!A4:C4,Sheet1!C4:D4)", "SUM(Sheet2!B4:D4,Sheet1!C4:D4)"},
{"A5", "SUM(Sheet2!A5:B5,C5:D5)", "SUM(Sheet2!A5:C5,C5:D5)", "SUM(Sheet2!B5:D5,C5:D5)"},
{"A6", "SUM(Sheet2!A6:B6,Sheet3!C6:D6)", "SUM(Sheet2!A6:C6,Sheet3!C6:D6)", "SUM(Sheet2!B6:D6,Sheet3!C6:D6)"},
}
for _, preset := range tbl {
assert.NoError(t, f.SetCellFormula("Sheet1", preset[0], preset[1]))
}
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.InsertCols("Sheet2", "B", 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[2], formula)
}
// Test adjust formula on insert row in the top of the range
assert.NoError(t, f.InsertCols("Sheet2", "A", 1))
for _, preset := range tbl {
formula, err := f.GetCellFormula("Sheet1", preset[0])
assert.NoError(t, err)
assert.Equal(t, preset[3], formula)
}
})
t.Run("for_cross_sheet_ref_with_chart_sheet)", func(t *testing.T) {
assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line}))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
})
t.Run("for_array_formula_cell", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
formulaType, ref := STCellFormulaTypeArray, "C1:C2"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
result, err := f.CalcCellValue("Sheet1", "D2")
assert.NoError(t, err)
assert.Equal(t, "2", result)
result, err = f.CalcCellValue("Sheet1", "D3")
assert.NoError(t, err)
assert.Equal(t, "12", result)
// Test adjust array formula with invalid range reference
formulaType, ref = STCellFormulaTypeArray, "E1:E2"
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", "XFD1:XFD1", FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
})
}
func TestAdjustVolatileDeps(t *testing.T) {
f := NewFile()
f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`|
|
`, NameSpaceSpreadSheet.Value)))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
assert.Equal(t, "D3", f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr[1].R)
assert.NoError(t, f.RemoveCol("Sheet1", "D"))
assert.NoError(t, f.RemoveRow("Sheet1", 4))
assert.Len(t, f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr, 1)
f = NewFile()
f.Pkg.Store(defaultXMLPathVolatileDeps, MacintoshCyrillicCharset)
assert.EqualError(t, f.InsertRows("Sheet1", 2, 1), "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`
`, NameSpaceSpreadSheet.Value)))
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.InsertCols("Sheet1", "A", 1))
f.volatileDepsWriter()
}
func TestAdjustConditionalFormats(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "B1", &[]interface{}{1, nil, 1, 1}))
formatID, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "09600B"}, Fill: Fill{Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1}})
assert.NoError(t, err)
format := []ConditionalFormatOptions{
{
Type: "cell",
Criteria: "greater than",
Format: &formatID,
Value: "0",
},
}
for _, ref := range []string{"B1", "D1:E1"} {
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, format))
}
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, format, 1)
assert.Equal(t, format, opts["C1:D1"])
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ConditionalFormatting[0].SQRef = "-"
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.RemoveCol("Sheet1", "B"))
ws.(*xlsxWorksheet).ConditionalFormatting[0] = nil
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
t.Run("for_remove_conditional_formats_column", func(t *testing.T) {
f := NewFile()
format := []ConditionalFormatOptions{{
Type: "data_bar",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
}}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format))
assert.NoError(t, f.RemoveCol("Sheet1", "D"))
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, opts, 0)
})
t.Run("for_remove_conditional_formats_row", func(t *testing.T) {
f := NewFile()
format := []ConditionalFormatOptions{{
Type: "data_bar",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
}}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:E2", format))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "F2", format))
assert.NoError(t, f.RemoveRow("Sheet1", 2))
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, opts, 0)
})
t.Run("for_adjust_conditional_formats_row", func(t *testing.T) {
f := NewFile()
format := []ConditionalFormatOptions{{
Type: "data_bar",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
}}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format))
assert.NoError(t, f.RemoveRow("Sheet1", 1))
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, opts, 2)
assert.Equal(t, format, opts["D1:D2"])
assert.Equal(t, format, opts["D4:D4"])
})
}
func TestAdjustDataValidations(t *testing.T) {
f := NewFile()
dv := NewDataValidation(true)
dv.Sqref = "B1"
assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"}))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dvs, 0)
assert.NoError(t, f.SetCellValue("Sheet1", "F2", 1))
assert.NoError(t, f.SetCellValue("Sheet1", "F3", 2))
dv = NewDataValidation(true)
dv.Sqref = "C2:D3"
dv.SetSqrefDropList("$F$2:$F$3")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line}))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet2", "C1", &[]interface{}{1, 10}))
dv = NewDataValidation(true)
dv.Sqref = "C5:D6"
assert.NoError(t, dv.SetRange("Sheet2!C1", "Sheet2!D1", DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetError(DataValidationErrorStyleStop, "error title", "error body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
assert.NoError(t, f.RemoveCol("Sheet2", "B"))
dvs, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "B2:C3", dvs[0].Sqref)
assert.Equal(t, "$E$2:$E$3", dvs[0].Formula1)
assert.Equal(t, "B5:C6", dvs[1].Sqref)
assert.Equal(t, "Sheet2!B1", dvs[1].Formula1)
assert.Equal(t, "Sheet2!C1", dvs[1].Formula2)
dv = NewDataValidation(true)
dv.Sqref = "C8:D10"
assert.NoError(t, dv.SetDropList([]string{`A<`, `B>`, `C"`, "D\t", `E'`, `F`}))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
dvs, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "\"A<,B>,C\",D\t,E',F\"", dvs[2].Formula1)
// Test adjust data validation with multiple cell range
dv = NewDataValidation(true)
dv.Sqref = "G1:G3 H1:H3 A3:A1048576"
assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"}))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
dvs, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "G1:G4 H1:H4 A4:A1048576", dvs[3].Sqref)
dv = NewDataValidation(true)
dv.Sqref = "C5:D6"
assert.NoError(t, dv.SetRange("Sheet1!A1048576", "Sheet1!XFD1", DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetError(DataValidationErrorStyleStop, "error title", "error body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "-"
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.RemoveCol("Sheet1", "B"))
ws.(*xlsxWorksheet).DataValidations.DataValidation[0] = nil
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
ws.(*xlsxWorksheet).DataValidations = nil
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.adjustDataValidations(nil, "Sheet1", columns, 0, 0, 1), "XML syntax error on line 1: invalid UTF-8")
t.Run("for_escaped_data_validation_rules_formula", func(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
dv := NewDataValidation(true)
dv.Sqref = "A1"
assert.NoError(t, dv.SetDropList([]string{"option1", strings.Repeat("\"", 4)}))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
// The double quote symbol in none formula data validation rules will be escaped in the Kingsoft WPS Office
formula := strings.ReplaceAll(fmt.Sprintf("\"option1, %s", strings.Repeat("\"", 9)), "\"", """)
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Formula1.Content = formula
assert.NoError(t, f.RemoveCol("Sheet2", "A"))
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, formula, dvs[0].Formula1)
})
t.Run("no_data_validations_on_first_sheet", func(t *testing.T) {
f := NewFile()
// Add Sheet2 and set a data validation
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
dv := NewDataValidation(true)
dv.Sqref = "C5:D6"
assert.NoError(t, f.AddDataValidation("Sheet2", dv))
// Adjust Sheet2 by removing a column
assert.NoError(t, f.RemoveCol("Sheet2", "A"))
// Verify that data validations on Sheet2 are adjusted correctly
dvs, err = f.GetDataValidations("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "B5:C6", dvs[0].Sqref) // Adjusted range
})
}
func TestAdjustDrawings(t *testing.T) {
f := NewFile()
// Test add pictures to sheet with positioning
assert.NoError(t, f.AddPicture("Sheet1", "B2", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "B11", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "oneCell"}))
assert.NoError(t, f.AddPicture("Sheet1", "B21", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "absolute"}))
// Test adjust pictures on inserting columns and rows
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
assert.NoError(t, f.InsertRows("Sheet1", 15, 1))
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"D3", "B21", "D13"}, cells)
wb := filepath.Join("test", "TestAdjustDrawings.xlsx")
assert.NoError(t, f.SaveAs(wb))
// Test adjust pictures on deleting columns and rows
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
assert.NoError(t, f.RemoveRow("Sheet1", 1))
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"C2", "B21", "C12"}, cells)
// Test adjust existing pictures on inserting columns and rows
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet1", "D", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
assert.NoError(t, f.InsertRows("Sheet1", 16, 1))
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"F4", "B21", "F15"}, cells)
// Test adjust drawings with unsupported charset
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), "XML syntax error on line 1: invalid UTF-8")
errors := []error{ErrColumnNumber, ErrColumnNumber, ErrMaxRows, ErrMaxRows}
cells = []string{"XFD1", "XFB1"}
for i, cell := range cells {
f = NewFile()
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil))
assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.SaveAs(wb))
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1))
}
errors = []error{ErrMaxRows, ErrMaxRows}
cells = []string{"A1048576", "A1048570"}
for i, cell := range cells {
f = NewFile()
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil))
assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.SaveAs(wb))
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1))
}
a := xdrCellAnchor{}
assert.NoError(t, a.adjustDrawings(columns, 0, 0))
p := xlsxCellAnchorPos{}
assert.NoError(t, p.adjustDrawings(columns, 0, 0, ""))
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`000
0101
0`))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+fmt.Sprintf(`%d0
`, MaxColumns)))
assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1))
}
func TestAdjustDefinedNames(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
for _, dn := range []*DefinedName{
{Name: "Name1", RefersTo: "Sheet1!$XFD$1"},
{Name: "Name2", RefersTo: "Sheet2!$C$1", Scope: "Sheet1"},
{Name: "Name3", RefersTo: "Sheet2!$C$1:$D$2", Scope: "Sheet1"},
{Name: "Name4", RefersTo: "Sheet2!$C1:D$2"},
{Name: "Name5", RefersTo: "Sheet2!C$1:$D2"},
{Name: "Name6", RefersTo: "Sheet2!C:$D"},
{Name: "Name7", RefersTo: "Sheet2!$C:D"},
{Name: "Name8", RefersTo: "Sheet2!C:D"},
{Name: "Name9", RefersTo: "Sheet2!$C:$D"},
{Name: "Name10", RefersTo: "Sheet2!1:2"},
} {
assert.NoError(t, f.SetDefinedName(dn))
}
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet2", "A", 1))
assert.NoError(t, f.InsertRows("Sheet2", 1, 1))
definedNames := f.GetDefinedName()
for i, expected := range []string{
"Sheet1!$XFD$2",
"Sheet2!$D$2",
"Sheet2!$D$2:$E$3",
"Sheet2!$D1:D$3",
"Sheet2!C$2:$E2",
"Sheet2!C:$E",
"Sheet2!$D:D",
"Sheet2!C:D",
"Sheet2!$D:$E",
"Sheet2!1:2",
} {
assert.Equal(t, expected, definedNames[i].RefersTo)
}
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name1",
RefersTo: "Sheet1!$A$1",
Scope: "Sheet1",
}))
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
definedNames = f.GetDefinedName()
assert.Equal(t, "Sheet1!$A$1", definedNames[0].RefersTo)
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name1",
RefersTo: "'1.A & B C'!#REF!",
Scope: "Sheet1",
}))
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
definedNames = f.GetDefinedName()
assert.Equal(t, "'1.A & B C'!#REF!", definedNames[0].RefersTo)
f = NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.adjustDefinedNames("Sheet1", columns, 0, 0), "XML syntax error on line 1: invalid UTF-8")
}
================================================
FILE: calc.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"container/list"
"errors"
"fmt"
"math"
"math/big"
"math/cmplx"
"math/rand"
"net/url"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"unsafe"
"github.com/xuri/efp"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
const (
// Excel formula errors
formulaErrorDIV = "#DIV/0!"
formulaErrorNAME = "#NAME?"
formulaErrorNA = "#N/A"
formulaErrorNUM = "#NUM!"
formulaErrorVALUE = "#VALUE!"
formulaErrorREF = "#REF!"
formulaErrorNULL = "#NULL!"
formulaErrorSPILL = "#SPILL!"
formulaErrorCALC = "#CALC!"
formulaErrorGETTINGDATA = "#GETTING_DATA"
// Formula criteria condition enumeration
_ byte = iota
criteriaEq
criteriaLe
criteriaGe
criteriaNe
criteriaL
criteriaG
criteriaErr
criteriaRegexp
categoryWeightAndMass
categoryDistance
categoryTime
categoryPressure
categoryForce
categoryEnergy
categoryPower
categoryMagnetism
categoryTemperature
categoryVolumeAndLiquidMeasure
categoryArea
categoryInformation
categorySpeed
matchModeExact = 0
matchModeMinGreater = 1
matchModeMaxLess = -1
matchModeWildcard = 2
searchModeLinear = 1
searchModeReverseLinear = -1
searchModeAscBinary = 2
searchModeDescBinary = -2
maxFinancialIterations = 128
financialPrecision = 1.0e-08
// Date and time format regular expressions
monthRe = `((jan|january)|(feb|february)|(mar|march)|(apr|april)|(may)|(jun|june)|(jul|july)|(aug|august)|(sep|september)|(oct|october)|(nov|november)|(dec|december))`
df1 = `(([0-9])+)/(([0-9])+)/(([0-9])+)`
df2 = monthRe + ` (([0-9])+), (([0-9])+)`
df3 = `(([0-9])+)-(([0-9])+)-(([0-9])+)`
df4 = `(([0-9])+)-` + monthRe + `-(([0-9])+)`
datePrefix = `^((` + df1 + `|` + df2 + `|` + df3 + `|` + df4 + `) )?`
tfhh = `(([0-9])+) (am|pm)`
tfhhmm = `(([0-9])+):(([0-9])+)( (am|pm))?`
tfmmss = `(([0-9])+):(([0-9])+\.([0-9])+)( (am|pm))?`
tfhhmmss = `(([0-9])+):(([0-9])+):(([0-9])+(\.([0-9])+)?)( (am|pm))?`
timeSuffix = `( (` + tfhh + `|` + tfhhmm + `|` + tfmmss + `|` + tfhhmmss + `))?$`
)
var (
// wildcardTokenRE tokenizes an Excel wildcard pattern into tilde-escaped
// sequences, bare wildcards (* ?), or any other single character.
wildcardTokenRE = regexp.MustCompile(`~[*?~]|[*?]|[\s\S]`)
// wildcardPatternMap maps each token produced by wildcardTokenRE to its
// regular-expression equivalent. Tokens absent from the map are literals.
wildcardPatternMap = map[string]string{
"~*": regexp.QuoteMeta("*"),
"~?": regexp.QuoteMeta("?"),
"~~": regexp.QuoteMeta("~"),
"*": ".*",
"?": ".",
}
// wildcardBareTokens is the set of tokens that represent real wildcards.
wildcardBareTokens = map[string]bool{"*": true, "?": true}
// tokenPriority defined basic arithmetic operator priority
tokenPriority = map[string]int{
"^": 5,
"*": 4,
"/": 4,
"+": 3,
"-": 3,
"&": 2,
"=": 1,
"<>": 1,
"<": 1,
"<=": 1,
">": 1,
">=": 1,
}
month2num = map[string]int{
"january": 1,
"february": 2,
"march": 3,
"april": 4,
"may": 5,
"june": 6,
"july": 7,
"august": 8,
"september": 9,
"october": 10,
"november": 11,
"december": 12,
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"oct": 10,
"nov": 11,
"dec": 12,
}
dateFormats = map[string]*regexp.Regexp{
"mm/dd/yy": regexp.MustCompile(`^` + df1 + timeSuffix),
"mm dd, yy": regexp.MustCompile(`^` + df2 + timeSuffix),
"yy-mm-dd": regexp.MustCompile(`^` + df3 + timeSuffix),
"yy-mmStr-dd": regexp.MustCompile(`^` + df4 + timeSuffix),
}
timeFormats = map[string]*regexp.Regexp{
"hh": regexp.MustCompile(datePrefix + tfhh + `$`),
"hh:mm": regexp.MustCompile(datePrefix + tfhhmm + `$`),
"mm:ss": regexp.MustCompile(datePrefix + tfmmss + `$`),
"hh:mm:ss": regexp.MustCompile(datePrefix + tfhhmmss + `$`),
}
dateOnlyFormats = []*regexp.Regexp{
regexp.MustCompile(`^` + df1 + `$`),
regexp.MustCompile(`^` + df2 + `$`),
regexp.MustCompile(`^` + df3 + `$`),
regexp.MustCompile(`^` + df4 + `$`),
}
addressFmtMaps = map[string]func(col, row int) (string, error){
"1_TRUE": func(col, row int) (string, error) {
return CoordinatesToCellName(col, row, true)
},
"1_FALSE": func(col, row int) (string, error) {
return fmt.Sprintf("R%dC%d", row, col), nil
},
"2_TRUE": func(col, row int) (string, error) {
column, err := ColumnNumberToName(col)
if err != nil {
return "", err
}
return fmt.Sprintf("%s$%d", column, row), nil
},
"2_FALSE": func(col, row int) (string, error) {
return fmt.Sprintf("R%dC[%d]", row, col), nil
},
"3_TRUE": func(col, row int) (string, error) {
column, err := ColumnNumberToName(col)
if err != nil {
return "", err
}
return fmt.Sprintf("$%s%d", column, row), nil
},
"3_FALSE": func(col, row int) (string, error) {
return fmt.Sprintf("R[%d]C%d", row, col), nil
},
"4_TRUE": func(col, row int) (string, error) {
return CoordinatesToCellName(col, row, false)
},
"4_FALSE": func(col, row int) (string, error) {
return fmt.Sprintf("R[%d]C[%d]", row, col), nil
},
}
formulaFnNameReplacer = strings.NewReplacer("_xlfn.", "", ".", "dot")
formulaFormats = []*regexp.Regexp{
regexp.MustCompile(`^(\d+)$`),
regexp.MustCompile(`^=(.*)$`),
regexp.MustCompile(`^<>(.*)$`),
regexp.MustCompile(`^<=(.*)$`),
regexp.MustCompile(`^>=(.*)$`),
regexp.MustCompile(`^<(.*)$`),
regexp.MustCompile(`^>(.*)$`),
}
formulaCriterias = []byte{
criteriaEq,
criteriaEq,
criteriaNe,
criteriaLe,
criteriaGe,
criteriaL,
criteriaG,
}
// defines numbers text in the Thai used for the BAHTTEXT formula function.
th0 = "\u0E28\u0E39\u0E19\u0E22\u0E4C"
th1 = "\u0E2B\u0E19\u0E36\u0E48\u0E07"
th2 = "\u0E2A\u0E2D\u0E07"
th3 = "\u0E2A\u0E32\u0E21"
th4 = "\u0E2A\u0E35\u0E48"
th5 = "\u0E2B\u0E49\u0E32"
th6 = "\u0E2B\u0E01"
th7 = "\u0E40\u0E08\u0E47\u0E14"
th8 = "\u0E41\u0E1B\u0E14"
th9 = "\u0E40\u0E01\u0E49\u0E32"
th10 = "\u0E2A\u0E34\u0E1A"
th11 = "\u0E40\u0E2D\u0E47\u0E14"
th20 = "\u0E22\u0E35\u0E48"
th1e2 = "\u0E23\u0E49\u0E2D\u0E22"
th1e3 = "\u0E1E\u0E31\u0E19"
th1e4 = "\u0E2B\u0E21\u0E37\u0E48\u0E19"
th1e5 = "\u0E41\u0E2A\u0E19"
th1e6 = "\u0E25\u0E49\u0E32\u0E19"
thDot0 = "\u0E16\u0E49\u0E27\u0E19"
thBaht = "\u0E1A\u0E32\u0E17"
thSatang = "\u0E2A\u0E15\u0E32\u0E07\u0E04\u0E4C"
thMinus = "\u0E25\u0E1A"
)
// calcContext defines the formula execution context.
type calcContext struct {
mu sync.Mutex
entry string
maxCalcIterations uint
iterations map[string]uint
iterationsCache map[string]formulaArg
}
// cellRef defines the structure of a cell reference.
type cellRef struct {
Col int
Row int
Sheet string
}
// cellRef defines the structure of a cell range.
type cellRange struct {
From cellRef
To cellRef
}
// formulaCriteria defined formula criteria parser result.
type formulaCriteria struct {
Type byte
Condition formulaArg
}
// ArgType is the type of formula argument type.
type ArgType byte
// Formula argument types enumeration.
const (
ArgUnknown ArgType = iota
ArgNumber
ArgString
ArgList
ArgMatrix
ArgError
ArgEmpty
)
// formulaArg is the argument of a formula or function.
type formulaArg struct {
SheetName string
Number float64
String string
List []formulaArg
Matrix [][]formulaArg
Boolean bool
Error string
Type ArgType
cellRefs, cellRanges *list.List
}
// Value returns a string data type of the formula argument.
func (fa formulaArg) Value() (value string) {
switch fa.Type {
case ArgNumber:
if fa.Boolean {
if fa.Number == 0 {
return "FALSE"
}
return "TRUE"
}
return fmt.Sprintf("%g", fa.Number)
case ArgString:
return fa.String
case ArgMatrix:
if args := fa.ToList(); len(args) > 0 {
return args[0].Value()
}
case ArgError:
return fa.Error
}
return
}
// ToNumber returns a formula argument with number data type.
func (fa formulaArg) ToNumber() formulaArg {
var n float64
var err error
switch fa.Type {
case ArgString:
n, err = strconv.ParseFloat(fa.String, 64)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
n = fa.Number
case ArgMatrix:
if args := fa.ToList(); len(args) > 0 {
return args[0].ToNumber()
}
}
return newNumberFormulaArg(n)
}
// ToBool returns a formula argument with boolean data type.
func (fa formulaArg) ToBool() formulaArg {
var b bool
var err error
switch fa.Type {
case ArgString:
b, err = strconv.ParseBool(fa.String)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
if fa.Number == 1 {
b = true
}
}
return newBoolFormulaArg(b)
}
// ToList returns a formula argument with array data type.
func (fa formulaArg) ToList() []formulaArg {
switch fa.Type {
case ArgMatrix:
var args []formulaArg
for _, row := range fa.Matrix {
args = append(args, row...)
}
return args
case ArgList:
return fa.List
case ArgNumber, ArgString, ArgError, ArgUnknown:
return []formulaArg{fa}
}
return nil
}
// formulaFuncs is the type of the formula functions.
type formulaFuncs struct {
f *File
ctx *calcContext
sheet, cell string
}
// implicitIntersect applies Excel's implicit intersection to a matrix argument.
// For a non-array formula, when a whole-column or whole-row reference is passed
// to a scalar function, Excel resolves it to the single cell in the same row
// (or column) as the formula cell. If the argument is not a matrix, it is
// returned unchanged.
func (fn *formulaFuncs) implicitIntersect(arg formulaArg) formulaArg {
if arg.Type != ArgMatrix {
return arg
}
_, row, err := CellNameToCoordinates(fn.cell)
if err != nil {
return arg
}
// row is 1-based; matrix is 0-indexed
idx := row - 1
if idx >= 0 && idx < len(arg.Matrix) && len(arg.Matrix[idx]) > 0 {
return arg.Matrix[idx][0]
}
return arg
}
// CalcCellValue provides a function to get calculated cell value. This feature
// is currently in working processing. Iterative calculation, implicit
// intersection, explicit intersection, array formula, table formula and some
// other formulas are not supported currently.
//
// Supported formula functions:
//
// ABS
// ACCRINT
// ACCRINTM
// ACOS
// ACOSH
// ACOT
// ACOTH
// ADDRESS
// AGGREGATE
// AMORDEGRC
// AMORLINC
// AND
// ARABIC
// ARRAYTOTEXT
// ASIN
// ASINH
// ATAN
// ATAN2
// ATANH
// AVEDEV
// AVERAGE
// AVERAGEA
// AVERAGEIF
// AVERAGEIFS
// BAHTTEXT
// BASE
// BESSELI
// BESSELJ
// BESSELK
// BESSELY
// BETA.DIST
// BETA.INV
// BETADIST
// BETAINV
// BIN2DEC
// BIN2HEX
// BIN2OCT
// BINOM.DIST
// BINOM.DIST.RANGE
// BINOM.INV
// BINOMDIST
// BITAND
// BITLSHIFT
// BITOR
// BITRSHIFT
// BITXOR
// CEILING
// CEILING.MATH
// CEILING.PRECISE
// CHAR
// CHIDIST
// CHIINV
// CHISQ.DIST
// CHISQ.DIST.RT
// CHISQ.INV
// CHISQ.INV.RT
// CHISQ.TEST
// CHITEST
// CHOOSE
// CLEAN
// CODE
// COLUMN
// COLUMNS
// COMBIN
// COMBINA
// COMPLEX
// CONCAT
// CONCATENATE
// CONFIDENCE
// CONFIDENCE.NORM
// CONFIDENCE.T
// CONVERT
// CORREL
// COS
// COSH
// COT
// COTH
// COUNT
// COUNTA
// COUNTBLANK
// COUNTIF
// COUNTIFS
// COUPDAYBS
// COUPDAYS
// COUPDAYSNC
// COUPNCD
// COUPNUM
// COUPPCD
// COVAR
// COVARIANCE.P
// COVARIANCE.S
// CRITBINOM
// CSC
// CSCH
// CUMIPMT
// CUMPRINC
// DATE
// DATEDIF
// DATEVALUE
// DAVERAGE
// DAY
// DAYS
// DAYS360
// DB
// DBCS
// DCOUNT
// DCOUNTA
// DDB
// DEC2BIN
// DEC2HEX
// DEC2OCT
// DECIMAL
// DEGREES
// DELTA
// DEVSQ
// DGET
// DISC
// DMAX
// DMIN
// DOLLAR
// DOLLARDE
// DOLLARFR
// DPRODUCT
// DSTDEV
// DSTDEVP
// DSUM
// DURATION
// DVAR
// DVARP
// EDATE
// EFFECT
// ENCODEURL
// EOMONTH
// ERF
// ERF.PRECISE
// ERFC
// ERFC.PRECISE
// ERROR.TYPE
// EUROCONVERT
// EVEN
// EXACT
// EXP
// EXPON.DIST
// EXPONDIST
// F.DIST
// F.DIST.RT
// F.INV
// F.INV.RT
// F.TEST
// FACT
// FACTDOUBLE
// FALSE
// FDIST
// FIND
// FINDB
// FINV
// FISHER
// FISHERINV
// FIXED
// FLOOR
// FLOOR.MATH
// FLOOR.PRECISE
// FORECAST
// FORECAST.LINEAR
// FORMULATEXT
// FREQUENCY
// FTEST
// FV
// FVSCHEDULE
// GAMMA
// GAMMA.DIST
// GAMMA.INV
// GAMMADIST
// GAMMAINV
// GAMMALN
// GAMMALN.PRECISE
// GAUSS
// GCD
// GEOMEAN
// GESTEP
// GROWTH
// HARMEAN
// HEX2BIN
// HEX2DEC
// HEX2OCT
// HLOOKUP
// HOUR
// HYPERLINK
// HYPGEOM.DIST
// HYPGEOMDIST
// IF
// IFERROR
// IFNA
// IFS
// IMABS
// IMAGINARY
// IMARGUMENT
// IMCONJUGATE
// IMCOS
// IMCOSH
// IMCOT
// IMCSC
// IMCSCH
// IMDIV
// IMEXP
// IMLN
// IMLOG10
// IMLOG2
// IMPOWER
// IMPRODUCT
// IMREAL
// IMSEC
// IMSECH
// IMSIN
// IMSINH
// IMSQRT
// IMSUB
// IMSUM
// IMTAN
// INDEX
// INDIRECT
// INT
// INTERCEPT
// INTRATE
// IPMT
// IRR
// ISBLANK
// ISERR
// ISERROR
// ISEVEN
// ISFORMULA
// ISLOGICAL
// ISNA
// ISNONTEXT
// ISNUMBER
// ISO.CEILING
// ISODD
// ISOWEEKNUM
// ISPMT
// ISREF
// ISTEXT
// KURT
// LARGE
// LCM
// LEFT
// LEFTB
// LEN
// LENB
// LN
// LOG
// LOG10
// LOGINV
// LOGNORM.DIST
// LOGNORM.INV
// LOGNORMDIST
// LOOKUP
// LOWER
// MATCH
// MAX
// MAXA
// MAXIFS
// MDETERM
// MDURATION
// MEDIAN
// MID
// MIDB
// MIN
// MINA
// MINIFS
// MINUTE
// MINVERSE
// MIRR
// MMULT
// MOD
// MODE
// MODE.MULT
// MODE.SNGL
// MONTH
// MROUND
// MULTINOMIAL
// MUNIT
// N
// NA
// NEGBINOM.DIST
// NEGBINOMDIST
// NETWORKDAYS
// NETWORKDAYS.INTL
// NOMINAL
// NORM.DIST
// NORM.INV
// NORM.S.DIST
// NORM.S.INV
// NORMDIST
// NORMINV
// NORMSDIST
// NORMSINV
// NOT
// NOW
// NPER
// NPV
// OCT2BIN
// OCT2DEC
// OCT2HEX
// ODD
// ODDFPRICE
// ODDFYIELD
// ODDLPRICE
// ODDLYIELD
// OR
// PDURATION
// PEARSON
// PERCENTILE
// PERCENTILE.EXC
// PERCENTILE.INC
// PERCENTRANK
// PERCENTRANK.EXC
// PERCENTRANK.INC
// PERMUT
// PERMUTATIONA
// PHI
// PI
// PMT
// POISSON
// POISSON.DIST
// POWER
// PPMT
// PRICE
// PRICEDISC
// PRICEMAT
// PROB
// PRODUCT
// PROPER
// PV
// QUARTILE
// QUARTILE.EXC
// QUARTILE.INC
// QUOTIENT
// RADIANS
// RAND
// RANDBETWEEN
// RANK
// RANK.EQ
// RATE
// RECEIVED
// REPLACE
// REPLACEB
// REPT
// RIGHT
// RIGHTB
// ROMAN
// ROUND
// ROUNDDOWN
// ROUNDUP
// ROW
// ROWS
// RRI
// RSQ
// SEARCH
// SEARCHB
// SEC
// SECH
// SECOND
// SERIESSUM
// SHEET
// SHEETS
// SIGN
// SIN
// SINH
// SKEW
// SKEW.P
// SLN
// SLOPE
// SMALL
// SORTBY
// SQRT
// SQRTPI
// STANDARDIZE
// STDEV
// STDEV.P
// STDEV.S
// STDEVA
// STDEVP
// STDEVPA
// STEYX
// SUBSTITUTE
// SUBTOTAL
// SUM
// SUMIF
// SUMIFS
// SUMPRODUCT
// SUMSQ
// SUMX2MY2
// SUMX2PY2
// SUMXMY2
// SWITCH
// SYD
// T
// T.DIST
// T.DIST.2T
// T.DIST.RT
// T.INV
// T.INV.2T
// T.TEST
// TAN
// TANH
// TBILLEQ
// TBILLPRICE
// TBILLYIELD
// TDIST
// TEXT
// TEXTAFTER
// TEXTBEFORE
// TEXTJOIN
// TIME
// TIMEVALUE
// TINV
// TODAY
// TRANSPOSE
// TREND
// TRIM
// TRIMMEAN
// TRUE
// TRUNC
// TTEST
// TYPE
// UNICHAR
// UNICODE
// UNIQUE
// UPPER
// VALUE
// VALUETOTEXT
// VAR
// VAR.P
// VAR.S
// VARA
// VARP
// VARPA
// VDB
// VLOOKUP
// WEEKDAY
// WEEKNUM
// WEIBULL
// WEIBULL.DIST
// WORKDAY
// WORKDAY.INTL
// XIRR
// XLOOKUP
// XNPV
// XOR
// YEAR
// YEARFRAC
// YIELD
// YIELDDISC
// YIELDMAT
// Z.TEST
// ZTEST
func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string, err error) {
entry := sheet + "!" + cell
if cachedResult, ok := f.calcCache.Load(entry); ok {
return cachedResult.(string), nil
}
options := f.getOptions(opts...)
var (
rawCellValue = options.RawCellValue
styleIdx int
token formulaArg
)
if token, err = f.calcCellValue(&calcContext{
entry: entry,
maxCalcIterations: options.MaxCalcIterations,
iterations: make(map[string]uint),
iterationsCache: make(map[string]formulaArg),
}, sheet, cell); err != nil {
result = token.String
return
}
if !rawCellValue {
styleIdx, _ = f.GetCellStyle(sheet, cell)
}
if token.Type == ArgNumber && !token.Boolean {
_, precision, decimal := isNumeric(token.Value())
if precision > 15 {
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'G', 15, 64))}, rawCellValue, CellTypeNumber)
if err == nil {
f.calcCache.Store(entry, result)
}
return
}
if !strings.HasPrefix(result, "0") {
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'f', -1, 64))}, rawCellValue, CellTypeNumber)
}
if err == nil {
f.calcCache.Store(entry, result)
}
return
}
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: token.Value()}, rawCellValue, CellTypeInlineString)
if err == nil {
f.calcCache.Store(entry, result)
}
return
}
// clearCalcCache clear all calculation related caches.
func (f *File) clearCalcCache() {
f.calcCache.Clear()
f.formulaArgCache.Clear()
}
// calcCellValue calculate cell value by given context, worksheet name and cell
// reference.
func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result formulaArg, err error) {
var formula string
if formula, err = f.getCellFormula(sheet, cell, true); err != nil {
return
}
ps := efp.ExcelParser()
tokens := ps.Parse(formula)
if tokens == nil {
return f.cellResolver(ctx, sheet, cell)
}
result, err = f.evalInfixExp(ctx, sheet, cell, tokens)
return
}
// getPriority calculate arithmetic operator priority.
func getPriority(token efp.Token) (pri int) {
pri = tokenPriority[token.TValue]
if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
pri = 6
}
if isBeginParenthesesToken(token) { // (
pri = 0
}
return
}
// newNumberFormulaArg constructs a number formula argument.
func newNumberFormulaArg(n float64) formulaArg {
if math.IsNaN(n) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return formulaArg{Type: ArgNumber, Number: n}
}
// newStringFormulaArg constructs a string formula argument.
func newStringFormulaArg(s string) formulaArg {
return formulaArg{Type: ArgString, String: s}
}
// newMatrixFormulaArg constructs a matrix formula argument.
func newMatrixFormulaArg(m [][]formulaArg) formulaArg {
return formulaArg{Type: ArgMatrix, Matrix: m}
}
// newListFormulaArg create a list formula argument.
func newListFormulaArg(l []formulaArg) formulaArg {
return formulaArg{Type: ArgList, List: l}
}
// newBoolFormulaArg constructs a boolean formula argument.
func newBoolFormulaArg(b bool) formulaArg {
var n float64
if b {
n = 1
}
return formulaArg{Type: ArgNumber, Number: n, Boolean: true}
}
// newErrorFormulaArg create an error formula argument of a given type with a
// specified error message.
func newErrorFormulaArg(formulaError, msg string) formulaArg {
return formulaArg{Type: ArgError, String: formulaError, Error: msg}
}
// newEmptyFormulaArg create an empty formula argument.
func newEmptyFormulaArg() formulaArg {
return formulaArg{Type: ArgEmpty}
}
// evalInfixExp evaluate syntax analysis by given infix expression after
// lexical analysis. Evaluate an infix expression containing formulas by
// stacks:
//
// opd - Operand
// opt - Operator
// opf - Operation formula
// opfd - Operand of the operation formula
// opft - Operator of the operation formula
// args - Arguments list of the operation formula
//
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.Token) (formulaArg, error) {
var (
err error
inArray, inArrayRow bool
formulaArray [][]formulaArg
formulaArrayRow []formulaArg
opdStack, optStack, opfStack = NewStack(), NewStack(), NewStack()
opfdStack, opftStack, argsStack = NewStack(), NewStack(), NewStack()
)
for i := 0; i < len(tokens); i++ {
token := tokens[i]
// out of function stack
if opfStack.Len() == 0 {
if err = f.parseToken(ctx, sheet, token, opdStack, optStack); err != nil {
return newEmptyFormulaArg(), err
}
}
// function start
if isFunctionStartToken(token) {
if token.TValue == "ARRAY" {
inArray, formulaArray = true, [][]formulaArg{}
continue
}
if token.TValue == "ARRAYROW" {
inArrayRow, formulaArrayRow = true, []formulaArg{}
continue
}
opfStack.Push(token)
argsStack.Push(list.New().Init())
opftStack.Push(token) // to know which operators belong to a function use the function as a separator
continue
}
// in function stack, walk 2 token at once
if opfStack.Len() > 0 {
var nextToken efp.Token
if i+1 < len(tokens) {
nextToken = tokens[i+1]
}
// current token is args or range, skip next token, order required: parse reference first
if token.TSubType == efp.TokenSubTypeRange {
if opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) {
refTo := f.getDefinedNameRefTo(token.TValue, sheet)
if refTo != "" {
token.TValue = refTo
}
// parse reference: must reference at here
result, err := f.parseReference(ctx, sheet, token.TValue)
if err != nil {
return result, err
}
opfdStack.Push(result)
continue
}
if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction {
// parse reference: reference or range at here
refTo := f.getDefinedNameRefTo(token.TValue, sheet)
if refTo != "" {
token.TValue = refTo
}
result, err := f.parseReference(ctx, sheet, token.TValue)
if err != nil {
return result, err
}
// when current token is range, next token is argument and opfdStack not empty,
// should push value to opfdStack and continue
if nextToken.TType == efp.TokenTypeArgument && !opfdStack.Empty() {
opfdStack.Push(result)
continue
}
argsStack.Peek().(*list.List).PushBack(result)
continue
}
}
// check current token is opft
if err = f.parseToken(ctx, sheet, token, opfdStack, opftStack); err != nil {
return newEmptyFormulaArg(), err
}
// current token is arg
if token.TType == efp.TokenTypeArgument {
for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) {
// calculate trigger
topOpt := opftStack.Peek().(efp.Token)
if err := calculate(opfdStack, topOpt); err != nil {
argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error()))
}
opftStack.Pop()
}
if !opfdStack.Empty() {
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg))
}
continue
}
if inArrayRow && isOperand(token) {
formulaArrayRow = append(formulaArrayRow, opfdStack.Pop().(formulaArg))
continue
}
if inArrayRow && isFunctionStopToken(token) {
formulaArray = append(formulaArray, formulaArrayRow)
inArrayRow = false
continue
}
if inArray && isFunctionStopToken(token) {
argsStack.Peek().(*list.List).PushBack(newMatrixFormulaArg(formulaArray))
inArray = false
continue
}
if errArg := f.evalInfixExpFunc(ctx, sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); errArg.Type == ArgError {
return errArg, errors.New(errArg.Error)
}
}
}
for optStack.Len() != 0 {
topOpt := optStack.Peek().(efp.Token)
if err = calculate(opdStack, topOpt); err != nil {
return newEmptyFormulaArg(), err
}
optStack.Pop()
}
if opdStack.Len() == 0 {
return newEmptyFormulaArg(), ErrInvalidFormula
}
return opdStack.Peek().(formulaArg), err
}
// evalInfixExpFunc evaluate formula function in the infix expression.
func (f *File) evalInfixExpFunc(ctx *calcContext, sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) formulaArg {
if !isFunctionStopToken(token) {
return newEmptyFormulaArg()
}
prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack)
// call formula function to evaluate
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell, ctx: ctx},
formulaFnNameReplacer.Replace(opfStack.Peek().(efp.Token).TValue),
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
if arg.Type == ArgError && opfStack.Len() == 1 {
return arg
}
argsStack.Pop()
opftStack.Pop() // remove current function separator
opfStack.Pop()
if opfStack.Len() > 0 { // still in function stack
if nextToken.TType == efp.TokenTypeOperatorInfix || opftStack.Len() > 1 {
// mathematics calculate in formula function
opfdStack.Push(arg)
return newEmptyFormulaArg()
}
argsStack.Peek().(*list.List).PushBack(arg)
return newEmptyFormulaArg()
}
if arg.Type == ArgMatrix && len(arg.Matrix) > 0 && len(arg.Matrix[0]) > 0 {
opdStack.Push(arg.Matrix[0][0])
return newEmptyFormulaArg()
}
opdStack.Push(arg)
return newEmptyFormulaArg()
}
// prepareEvalInfixExp check the token and stack state for formula function
// evaluate.
func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) {
// current token is function stop
for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) {
// calculate trigger
topOpt := opftStack.Peek().(efp.Token)
if err := calculate(opfdStack, topOpt); err != nil {
argsStack.Peek().(*list.List).PushBack(newErrorFormulaArg(err.Error(), err.Error()))
opftStack.Pop()
continue
}
opftStack.Pop()
}
argument := true
if opftStack.Len() > 2 && opfdStack.Len() == 1 {
topOpt := opftStack.Pop()
if opftStack.Peek().(efp.Token).TType == efp.TokenTypeOperatorInfix {
argument = false
}
opftStack.Push(topOpt)
}
// push opfd to args
if argument && opfdStack.Len() > 0 {
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg))
}
}
// calcPow evaluate exponentiation arithmetic operations.
func calcPow(rOpd, lOpd formulaArg, opdStack *Stack) error {
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
}
rOpdVal := rOpd.ToNumber()
if rOpdVal.Type != ArgNumber {
return errors.New(rOpdVal.Value())
}
opdStack.Push(newNumberFormulaArg(math.Pow(lOpdVal.Number, rOpdVal.Number)))
return nil
}
// calcEq evaluate equal arithmetic operations.
func calcEq(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(strings.EqualFold(lOpd.Value(), rOpd.Value())))
return nil
}
opdStack.Push(newBoolFormulaArg(rOpd.Value() == lOpd.Value()))
return nil
}
// calcNEq evaluate not equal arithmetic operations.
func calcNEq(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(!strings.EqualFold(lOpd.Value(), rOpd.Value())))
return nil
}
opdStack.Push(newBoolFormulaArg(rOpd.Value() != lOpd.Value()))
return nil
}
// calcL evaluate less than arithmetic operations.
func calcL(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(lOpd.Number < rOpd.Number))
}
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == -1))
}
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(false))
}
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(true))
}
return nil
}
// calcLe evaluate less than or equal arithmetic operations.
func calcLe(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(lOpd.Number <= rOpd.Number))
}
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != 1))
}
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(false))
}
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(true))
}
return nil
}
// calcG evaluate greater than arithmetic operations.
func calcG(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(lOpd.Number > rOpd.Number))
}
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == 1))
}
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(true))
}
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(false))
}
return nil
}
// calcGe evaluate greater than or equal arithmetic operations.
func calcGe(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(lOpd.Number >= rOpd.Number))
}
if rOpd.Type == ArgString && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != -1))
}
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
opdStack.Push(newBoolFormulaArg(true))
}
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
opdStack.Push(newBoolFormulaArg(false))
}
return nil
}
// calcSplice evaluate splice '&' operations.
func calcSplice(rOpd, lOpd formulaArg, opdStack *Stack) error {
opdStack.Push(newStringFormulaArg(lOpd.Value() + rOpd.Value()))
return nil
}
// calcAdd evaluate addition arithmetic operations.
func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error {
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
}
rOpdVal := rOpd.ToNumber()
if rOpdVal.Type != ArgNumber {
return errors.New(rOpdVal.Value())
}
opdStack.Push(newNumberFormulaArg(lOpdVal.Number + rOpdVal.Number))
return nil
}
// calcSubtract evaluate subtraction arithmetic operations.
func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Value() == "" {
rOpd = newNumberFormulaArg(0)
}
if lOpd.Value() == "" {
lOpd = newNumberFormulaArg(0)
}
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
}
rOpdVal := rOpd.ToNumber()
if rOpdVal.Type != ArgNumber {
return errors.New(rOpdVal.Value())
}
opdStack.Push(newNumberFormulaArg(lOpdVal.Number - rOpdVal.Number))
return nil
}
// calcMultiply evaluate multiplication arithmetic operations.
func calcMultiply(rOpd, lOpd formulaArg, opdStack *Stack) error {
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
}
rOpdVal := rOpd.ToNumber()
if rOpdVal.Type != ArgNumber {
return errors.New(rOpdVal.Value())
}
opdStack.Push(newNumberFormulaArg(lOpdVal.Number * rOpdVal.Number))
return nil
}
// calcDiv evaluate division arithmetic operations.
func calcDiv(rOpd, lOpd formulaArg, opdStack *Stack) error {
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
}
rOpdVal := rOpd.ToNumber()
if rOpdVal.Type != ArgNumber {
return errors.New(rOpdVal.Value())
}
if rOpdVal.Number == 0 {
return errors.New(formulaErrorDIV)
}
opdStack.Push(newNumberFormulaArg(lOpdVal.Number / rOpdVal.Number))
return nil
}
// calculate evaluate basic arithmetic operations.
func calculate(opdStack *Stack, opt efp.Token) error {
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorPrefix {
if opdStack.Len() < 1 {
return ErrInvalidFormula
}
opd := opdStack.Pop().(formulaArg)
opdStack.Push(newNumberFormulaArg(0 - opd.ToNumber().Number))
}
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
if opdStack.Len() < 2 {
return ErrInvalidFormula
}
rOpd := opdStack.Pop().(formulaArg)
lOpd := opdStack.Pop().(formulaArg)
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
return err
}
}
tokenCalcFunc := map[string]func(rOpd, lOpd formulaArg, opdStack *Stack) error{
"^": calcPow,
"*": calcMultiply,
"/": calcDiv,
"+": calcAdd,
"=": calcEq,
"<>": calcNEq,
"<": calcL,
"<=": calcLe,
">": calcG,
">=": calcGe,
"&": calcSplice,
}
if fn, ok := tokenCalcFunc[opt.TValue]; ok {
if opdStack.Len() < 2 {
return ErrInvalidFormula
}
rOpd := opdStack.Pop().(formulaArg)
lOpd := opdStack.Pop().(formulaArg)
if opt.TValue != "&" {
if rOpd.Value() == "" {
rOpd = newNumberFormulaArg(0)
}
if lOpd.Value() == "" {
lOpd = newNumberFormulaArg(0)
}
}
if rOpd.Type == ArgError {
return errors.New(rOpd.Value())
}
if lOpd.Type == ArgError {
return errors.New(lOpd.Value())
}
return fn(rOpd, lOpd, opdStack)
}
return nil
}
// parseOperatorPrefixToken parse operator prefix token.
func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Token) (err error) {
if optStack.Len() == 0 {
optStack.Push(token)
return
}
tokenPriority := getPriority(token)
topOpt := optStack.Peek().(efp.Token)
topOptPriority := getPriority(topOpt)
if topOpt.TValue == "-" && topOpt.TType == efp.TokenTypeOperatorPrefix && token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
optStack.Pop()
return
}
if tokenPriority > topOptPriority {
optStack.Push(token)
return
}
for tokenPriority <= topOptPriority {
optStack.Pop()
if err = calculate(opdStack, topOpt); err != nil {
return
}
if optStack.Len() > 0 {
topOpt = optStack.Peek().(efp.Token)
topOptPriority = getPriority(topOpt)
continue
}
break
}
optStack.Push(token)
return
}
// isFunctionStartToken determine if the token is function start.
func isFunctionStartToken(token efp.Token) bool {
return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart
}
// isFunctionStopToken determine if the token is function stop.
func isFunctionStopToken(token efp.Token) bool {
return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop
}
// isBeginParenthesesToken determine if the token is begin parentheses: (.
func isBeginParenthesesToken(token efp.Token) bool {
return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart
}
// isEndParenthesesToken determine if the token is end parentheses: ).
func isEndParenthesesToken(token efp.Token) bool {
return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop
}
// isOperatorPrefixToken determine if the token is parse operator prefix
// token.
func isOperatorPrefixToken(token efp.Token) bool {
_, ok := tokenPriority[token.TValue]
return (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix)
}
// isOperand determine if the token is parse operand.
func isOperand(token efp.Token) bool {
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText || token.TSubType == efp.TokenSubTypeLogical)
}
// tokenToFormulaArg create a formula argument by given token.
func tokenToFormulaArg(token efp.Token) formulaArg {
switch token.TSubType {
case efp.TokenSubTypeLogical:
return newBoolFormulaArg(strings.EqualFold(token.TValue, "TRUE"))
case efp.TokenSubTypeNumber:
num, _ := strconv.ParseFloat(token.TValue, 64)
return newNumberFormulaArg(num)
default:
return newStringFormulaArg(token.TValue)
}
}
// formulaArgToToken create a token by given formula argument.
func formulaArgToToken(arg formulaArg) efp.Token {
switch arg.Type {
case ArgNumber:
if arg.Boolean {
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeLogical}
}
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}
default:
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeText}
}
}
// parseToken parse basic arithmetic operator priority and evaluate based on
// operators and operands.
func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdStack, optStack *Stack) error {
// parse reference: must reference at here
if token.TSubType == efp.TokenSubTypeRange {
refTo := f.getDefinedNameRefTo(token.TValue, sheet)
if refTo != "" {
token.TValue = refTo
}
result, err := f.parseReference(ctx, sheet, token.TValue)
if err != nil {
return errors.New(formulaErrorNAME)
}
token = formulaArgToToken(result)
}
if isOperatorPrefixToken(token) {
if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
return err
}
}
if isBeginParenthesesToken(token) { // (
optStack.Push(token)
}
if isEndParenthesesToken(token) { // )
for !isBeginParenthesesToken(optStack.Peek().(efp.Token)) { // != (
topOpt := optStack.Peek().(efp.Token)
if err := calculate(opdStack, topOpt); err != nil {
return err
}
optStack.Pop()
}
optStack.Pop()
}
if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() {
topOpd := opdStack.Pop().(formulaArg)
opdStack.Push(newNumberFormulaArg(topOpd.Number / 100))
}
// opd
if isOperand(token) {
opdStack.Push(tokenToFormulaArg(token))
}
return nil
}
// parseRef parse reference for a cell, column name or row number.
func parseRef(ref string) (cellRef, bool, bool, error) {
var (
err, colErr, rowErr error
cr cellRef
cell = ref
tokens = strings.Split(ref, "!")
)
if len(tokens) == 2 { // have a worksheet
cr.Sheet, cell = strings.TrimSuffix(strings.TrimPrefix(tokens[0], "'"), "'"), tokens[1]
}
if cr.Col, cr.Row, err = CellNameToCoordinates(cell); err != nil {
if cr.Col, colErr = ColumnNameToNumber(cell); colErr == nil { // cast to column
return cr, true, false, nil
}
if cr.Row, rowErr = strconv.Atoi(cell); rowErr == nil { // cast to row
if cr.Row < 1 || cr.Row > TotalRows {
return cr, false, false, err
}
return cr, false, true, nil
}
return cr, false, false, err
}
return cr, false, false, err
}
// prepareCellRange checking and convert cell reference to a cell range.
func (cr *cellRange) prepareCellRange(col, row bool, cellRef cellRef) error {
if col {
cellRef.Row = TotalRows
}
if row {
cellRef.Col = MaxColumns
}
if cellRef.Sheet == "" {
cellRef.Sheet = cr.From.Sheet
}
if cr.From.Sheet != cellRef.Sheet || cr.To.Sheet != cellRef.Sheet {
return errors.New("invalid reference")
}
if cr.From.Col > cellRef.Col {
cr.From.Col = cellRef.Col
}
if cr.From.Row > cellRef.Row {
cr.From.Row = cellRef.Row
}
if cr.To.Col < cellRef.Col {
cr.To.Col = cellRef.Col
}
if cr.To.Row < cellRef.Row {
cr.To.Row = cellRef.Row
}
return nil
}
// parseReference parse reference and extract values by given reference
// characters and default sheet name.
func (f *File) parseReference(ctx *calcContext, sheet, reference string) (formulaArg, error) {
reference = strings.ReplaceAll(reference, "$", "")
ranges, cellRanges, cellRefs := strings.Split(reference, ":"), list.New(), list.New()
if len(ranges) > 1 {
var cr cellRange
for i, ref := range ranges {
cellRef, col, row, err := parseRef(ref)
if err != nil {
return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
}
if i == 0 {
if col {
cellRef.Row = 1
}
if row {
cellRef.Col = 1
}
if cellRef.Sheet == "" {
cellRef.Sheet = sheet
}
cr.From, cr.To = cellRef, cellRef
continue
}
if err := cr.prepareCellRange(col, row, cellRef); err != nil {
return newErrorFormulaArg(formulaErrorNAME, err.Error()), err
}
}
cellRanges.PushBack(cr)
return f.rangeResolver(ctx, cellRefs, cellRanges)
}
cellRef, _, _, err := parseRef(reference)
if err != nil {
return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
}
if cellRef.Sheet == "" {
cellRef.Sheet = sheet
}
cellRefs.PushBack(cellRef)
return f.rangeResolver(ctx, cellRefs, cellRanges)
}
// prepareValueRange prepare value range.
func prepareValueRange(cr cellRange, valueRange []int) {
if cr.From.Row < valueRange[0] || valueRange[0] == 0 {
valueRange[0] = cr.From.Row
}
if cr.From.Col < valueRange[2] || valueRange[2] == 0 {
valueRange[2] = cr.From.Col
}
if cr.To.Row > valueRange[1] || valueRange[1] == 0 {
valueRange[1] = cr.To.Row
}
if cr.To.Col > valueRange[3] || valueRange[3] == 0 {
valueRange[3] = cr.To.Col
}
}
// prepareValueRef prepare value reference.
func prepareValueRef(cr cellRef, valueRange []int) {
if cr.Row < valueRange[0] || valueRange[0] == 0 {
valueRange[0] = cr.Row
}
if cr.Col < valueRange[2] || valueRange[2] == 0 {
valueRange[2] = cr.Col
}
if cr.Row > valueRange[1] || valueRange[1] == 0 {
valueRange[1] = cr.Row
}
if cr.Col > valueRange[3] || valueRange[3] == 0 {
valueRange[3] = cr.Col
}
}
// cellResolver calc cell value by given worksheet name, cell reference and context.
func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, error) {
var (
arg formulaArg
value string
err error
)
ref := sheet + "!" + cell
if cached, ok := f.formulaArgCache.Load(ref); ok {
return cached.(formulaArg), err
}
if formula, _ := f.getCellFormula(sheet, cell, true); len(formula) != 0 {
ctx.mu.Lock()
if ctx.entry != ref {
if ctx.iterations[ref] <= f.options.MaxCalcIterations {
ctx.iterations[ref]++
ctx.mu.Unlock()
arg, _ = f.calcCellValue(ctx, sheet, cell)
ctx.iterationsCache[ref] = arg
f.formulaArgCache.Store(ref, arg)
return arg, nil
}
ctx.mu.Unlock()
return ctx.iterationsCache[ref], nil
}
ctx.mu.Unlock()
}
if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil {
return arg, err
}
arg = newStringFormulaArg(value)
cellType, _ := f.GetCellType(sheet, cell)
switch cellType {
case CellTypeBool:
arg = arg.ToBool()
case CellTypeNumber, CellTypeUnset:
if arg.Value() == "" {
arg = newEmptyFormulaArg()
} else {
arg = arg.ToNumber()
}
case CellTypeInlineString, CellTypeSharedString:
case CellTypeFormula:
if value == "" {
arg = newEmptyFormulaArg()
}
case CellTypeDate:
if value, err = f.GetCellValue(sheet, cell); err == nil {
if num := newStringFormulaArg(value).ToNumber(); num.Type == ArgNumber {
arg = num
}
}
default:
arg = newErrorFormulaArg(value, value)
}
f.formulaArgCache.Store(ref, arg)
return arg, err
}
// rangeResolver extract value as string from given reference and range list.
// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
// be reference A1:B3.
func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List) (arg formulaArg, err error) {
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
// value range order: from row, to row, from column, to column
valueRange := []int{0, 0, 0, 0}
var sheet string
// prepare value range
for temp := cellRanges.Front(); temp != nil; temp = temp.Next() {
cr := temp.Value.(cellRange)
rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row}
_ = sortCoordinates(rng)
cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row = rng[0], rng[1], rng[2], rng[3]
prepareValueRange(cr, valueRange)
if cr.From.Sheet != "" {
sheet = cr.From.Sheet
}
}
for temp := cellRefs.Front(); temp != nil; temp = temp.Next() {
cr := temp.Value.(cellRef)
if cr.Sheet != "" {
sheet = cr.Sheet
}
prepareValueRef(cr, valueRange)
}
// extract value from ranges
if cellRanges.Len() > 0 {
arg.Type = ArgMatrix
var ws *xlsxWorksheet
ws, err = f.workSheetReader(sheet)
if err != nil {
return
}
// Detect whole column/row reference, limit to actual data range
if valueRange[1] == TotalRows {
actualMaxRow := 0
for _, rowData := range ws.SheetData.Row {
if rowData.R > actualMaxRow {
actualMaxRow = rowData.R
}
}
if actualMaxRow > 0 && actualMaxRow < TotalRows {
valueRange[1] = actualMaxRow
}
}
if valueRange[3] == MaxColumns {
actualMaxCol := 0
for _, rowData := range ws.SheetData.Row {
for _, cell := range rowData.C {
col, _, err := CellNameToCoordinates(cell.R)
if err == nil && col > actualMaxCol {
actualMaxCol = col
}
}
}
if actualMaxCol > 0 && actualMaxCol < MaxColumns {
valueRange[3] = actualMaxCol
}
}
for row := valueRange[0]; row <= valueRange[1]; row++ {
colMax := 0
if 0 < row && row <= len(ws.SheetData.Row) {
rowData := &ws.SheetData.Row[row-1]
colMax = min(valueRange[3], len(rowData.C))
}
var matrixRow []formulaArg
for col := valueRange[2]; col <= valueRange[3]; col++ {
value := newEmptyFormulaArg()
if col <= colMax {
var cell string
if cell, err = CoordinatesToCellName(col, row); err != nil {
return
}
if value, err = f.cellResolver(ctx, sheet, cell); err != nil {
return
}
}
matrixRow = append(matrixRow, value)
}
arg.Matrix = append(arg.Matrix, matrixRow)
}
return
}
// extract value from references
for temp := cellRefs.Front(); temp != nil; temp = temp.Next() {
cr := temp.Value.(cellRef)
var cell string
if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
return
}
if arg, err = f.cellResolver(ctx, cr.Sheet, cell); err != nil {
return
}
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
}
return
}
// callFuncByName calls the no error or only error return function with
// reflect by given receiver, name and parameters.
func callFuncByName(receiver interface{}, name string, params []reflect.Value) (arg formulaArg) {
function := reflect.ValueOf(receiver).MethodByName(name)
if function.IsValid() {
rt := function.Call(params)
if len(rt) == 0 {
return
}
arg = rt[0].Interface().(formulaArg)
return
}
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("not support %s function", name))
}
// formulaCriteriaParser parse formula criteria.
func formulaCriteriaParser(exp formulaArg) *formulaCriteria {
prepareValue := func(cond string) (expected float64, err error) {
percentile := 1.0
if strings.HasSuffix(cond, "%") {
cond = strings.TrimSuffix(cond, "%")
percentile /= 100
}
if expected, err = strconv.ParseFloat(cond, 64); err != nil {
return
}
expected *= percentile
return
}
fc, val := &formulaCriteria{}, exp.Value()
if val == "" {
return fc
}
for i, re := range formulaFormats {
if match := re.FindStringSubmatch(val); len(match) > 1 {
fc.Condition = newStringFormulaArg(match[1])
if num, err := prepareValue(match[1]); err == nil {
fc.Condition = newNumberFormulaArg(num)
}
fc.Type = formulaCriterias[i]
return fc
}
}
hasWildcard := false
pattern := wildcardTokenRE.ReplaceAllStringFunc(val, func(m string) string {
hasWildcard = hasWildcard || wildcardBareTokens[m]
if r, ok := wildcardPatternMap[m]; ok {
return r
}
return regexp.QuoteMeta(m)
})
if hasWildcard {
fc.Type, fc.Condition = criteriaRegexp, newStringFormulaArg("(?i)^"+pattern+"$")
return fc
}
fc.Type, fc.Condition = criteriaEq, newStringFormulaArg(
strings.NewReplacer("~~", "~", "~*", "*", "~?", "?").Replace(val),
)
if num := fc.Condition.ToNumber(); num.Type == ArgNumber {
fc.Condition = num
}
return fc
}
// formulaCriteriaEval evaluate formula criteria expression.
func formulaCriteriaEval(val formulaArg, criteria *formulaCriteria) (result bool, err error) {
s := NewStack()
tokenCalcFunc := map[byte]func(rOpd, lOpd formulaArg, opdStack *Stack) error{
criteriaEq: calcEq,
criteriaNe: calcNEq,
criteriaL: calcL,
criteriaLe: calcLe,
criteriaG: calcG,
criteriaGe: calcGe,
}
switch criteria.Type {
case criteriaEq, criteriaLe, criteriaGe, criteriaNe, criteriaL, criteriaG:
if fn, ok := tokenCalcFunc[criteria.Type]; ok {
if _ = fn(criteria.Condition, val, s); s.Len() > 0 {
return s.Pop().(formulaArg).Number == 1, err
}
}
case criteriaRegexp:
pattern := criteria.Condition.Value()
if !strings.HasPrefix(pattern, "^") {
pattern = "^" + pattern
}
if !strings.HasSuffix(pattern, "$") {
pattern = pattern + "$"
}
return regexp.MatchString(pattern, val.Value())
}
return
}
// Engineering Functions
// BESSELI function the modified Bessel function, which is equivalent to the
// Bessel function evaluated for purely imaginary arguments. The syntax of
// the Besseli function is:
//
// BESSELI(x,n)
func (fn *formulaFuncs) BESSELI(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BESSELI requires 2 numeric arguments")
}
return fn.bassel(argsList, true)
}
// BESSELJ function returns the Bessel function, Jn(x), for a specified order
// and value of x. The syntax of the function is:
//
// BESSELJ(x,n)
func (fn *formulaFuncs) BESSELJ(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BESSELJ requires 2 numeric arguments")
}
return fn.bassel(argsList, false)
}
// bassel is an implementation of the formula functions BESSELI and BESSELJ.
func (fn *formulaFuncs) bassel(argsList *list.List, modfied bool) formulaArg {
x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
if n.Type != ArgNumber {
return n
}
maxVal, x1 := 100, x.Number*0.5
x2 := x1 * x1
x1 = math.Pow(x1, n.Number)
n1, n2, n3, n4, add := fact(n.Number), 1.0, 0.0, n.Number, false
result := x1 / n1
t := result * 0.9
for result != t && maxVal != 0 {
x1 *= x2
n3++
n1 *= n3
n4++
n2 *= n4
t = result
r := x1 / n1 / n2
if modfied || add {
result += r
} else {
result -= r
}
maxVal--
add = !add
}
return newNumberFormulaArg(result)
}
// BESSELK function calculates the modified Bessel functions, Kn(x), which are
// also known as the hyperbolic Bessel Functions. These are the equivalent of
// the Bessel functions, evaluated for purely imaginary arguments. The syntax
// of the function is:
//
// BESSELK(x,n)
func (fn *formulaFuncs) BESSELK(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BESSELK requires 2 numeric arguments")
}
x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
if n.Type != ArgNumber {
return n
}
if x.Number <= 0 || n.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
var result float64
switch math.Floor(n.Number) {
case 0:
result = fn.besselK0(x)
case 1:
result = fn.besselK1(x)
default:
result = fn.besselK2(x, n)
}
return newNumberFormulaArg(result)
}
// besselK0 is an implementation of the formula function BESSELK.
func (fn *formulaFuncs) besselK0(x formulaArg) float64 {
var y float64
if x.Number <= 2 {
n2 := x.Number * 0.5
y = n2 * n2
args := list.New()
args.PushBack(x)
args.PushBack(newNumberFormulaArg(0))
return -math.Log(n2)*fn.BESSELI(args).Number +
(-0.57721566 + y*(0.42278420+y*(0.23069756+y*(0.3488590e-1+y*(0.262698e-2+y*
(0.10750e-3+y*0.74e-5))))))
}
y = 2 / x.Number
return math.Exp(-x.Number) / math.Sqrt(x.Number) *
(1.25331414 + y*(-0.7832358e-1+y*(0.2189568e-1+y*(-0.1062446e-1+y*
(0.587872e-2+y*(-0.251540e-2+y*0.53208e-3))))))
}
// besselK1 is an implementation of the formula function BESSELK.
func (fn *formulaFuncs) besselK1(x formulaArg) float64 {
var n2, y float64
if x.Number <= 2 {
n2 = x.Number * 0.5
y = n2 * n2
args := list.New()
args.PushBack(x)
args.PushBack(newNumberFormulaArg(1))
return math.Log(n2)*fn.BESSELI(args).Number +
(1+y*(0.15443144+y*(-0.67278579+y*(-0.18156897+y*(-0.1919402e-1+y*(-0.110404e-2+y*(-0.4686e-4)))))))/x.Number
}
y = 2 / x.Number
return math.Exp(-x.Number) / math.Sqrt(x.Number) *
(1.25331414 + y*(0.23498619+y*(-0.3655620e-1+y*(0.1504268e-1+y*(-0.780353e-2+y*
(0.325614e-2+y*(-0.68245e-3)))))))
}
// besselK2 is an implementation of the formula function BESSELK.
func (fn *formulaFuncs) besselK2(x, n formulaArg) float64 {
tox, bkm, bk, bkp := 2/x.Number, fn.besselK0(x), fn.besselK1(x), 0.0
for i := 1.0; i < n.Number; i++ {
bkp = math.FMA(i*tox, bk, bkm)
bkm = bk
bk = bkp
}
return bk
}
// BESSELY function returns the Bessel function, Yn(x), (also known as the
// Weber function or the Neumann function), for a specified order and value
// of x. The syntax of the function is:
//
// BESSELY(x,n)
func (fn *formulaFuncs) BESSELY(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BESSELY requires 2 numeric arguments")
}
x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
if n.Type != ArgNumber {
return n
}
if x.Number <= 0 || n.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
var result float64
switch math.Floor(n.Number) {
case 0:
result = fn.besselY0(x)
case 1:
result = fn.besselY1(x)
default:
result = fn.besselY2(x, n)
}
return newNumberFormulaArg(result)
}
// besselY0 is an implementation of the formula function BESSELY.
func (fn *formulaFuncs) besselY0(x formulaArg) float64 {
var y float64
if x.Number < 8 {
y = x.Number * x.Number
f1 := -2957821389.0 + y*(7062834065.0+y*(-512359803.6+y*(10879881.29+y*
(-86327.92757+y*228.4622733))))
f2 := 40076544269.0 + y*(745249964.8+y*(7189466.438+y*
(47447.26470+y*(226.1030244+y))))
args := list.New()
args.PushBack(x)
args.PushBack(newNumberFormulaArg(0))
return f1/f2 + 0.636619772*fn.BESSELJ(args).Number*math.Log(x.Number)
}
z := 8.0 / x.Number
y = z * z
xx := x.Number - 0.785398164
f1 := 1 + y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6)))
f2 := -0.1562499995e-1 + y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6+y*
(-0.934945152e-7))))
return math.Sqrt(0.636619772/x.Number) * (math.Sin(xx)*f1 + z*math.Cos(xx)*f2)
}
// besselY1 is an implementation of the formula function BESSELY.
func (fn *formulaFuncs) besselY1(x formulaArg) float64 {
if x.Number < 8 {
y := x.Number * x.Number
f1 := x.Number * (-0.4900604943e13 + y*(0.1275274390e13+y*(-0.5153438139e11+y*
(0.7349264551e9+y*(-0.4237922726e7+y*0.8511937935e4)))))
f2 := 0.2499580570e14 + y*(0.4244419664e12+y*(0.3733650367e10+y*(0.2245904002e8+y*
(0.1020426050e6+y*(0.3549632885e3+y)))))
args := list.New()
args.PushBack(x)
args.PushBack(newNumberFormulaArg(1))
return f1/f2 + 0.636619772*(fn.BESSELJ(args).Number*math.Log(x.Number)-1/x.Number)
}
return math.Sqrt(0.636619772/x.Number) * math.Sin(x.Number-2.356194491)
}
// besselY2 is an implementation of the formula function BESSELY.
func (fn *formulaFuncs) besselY2(x, n formulaArg) float64 {
tox, bym, by, byp := 2/x.Number, fn.besselY0(x), fn.besselY1(x), 0.0
for i := 1.0; i < n.Number; i++ {
byp = math.FMA(i*tox, by, -bym)
bym = by
by = byp
}
return by
}
// BIN2DEC function converts a Binary (a base-2 number) into a decimal number.
// The syntax of the function is:
//
// BIN2DEC(number)
func (fn *formulaFuncs) BIN2DEC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "BIN2DEC requires 1 numeric argument")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
return fn.bin2dec(token.Value())
}
// BIN2HEX function converts a Binary (Base 2) number into a Hexadecimal
// (Base 16) number. The syntax of the function is:
//
// BIN2HEX(number,[places])
func (fn *formulaFuncs) BIN2HEX(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX allows at most 2 arguments")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
decimal, newList := fn.bin2dec(token.Value()), list.New()
if decimal.Type != ArgNumber {
return decimal
}
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("BIN2HEX", newList)
}
// BIN2OCT function converts a Binary (Base 2) number into an Octal (Base 8)
// number. The syntax of the function is:
//
// BIN2OCT(number,[places])
func (fn *formulaFuncs) BIN2OCT(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT allows at most 2 arguments")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
decimal, newList := fn.bin2dec(token.Value()), list.New()
if decimal.Type != ArgNumber {
return decimal
}
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("BIN2OCT", newList)
}
// bin2dec is an implementation of the formula function BIN2DEC.
func (fn *formulaFuncs) bin2dec(number string) formulaArg {
decimal, length := 0.0, len(number)
for i := length; i > 0; i-- {
s := string(number[length-i])
if i == 10 && s == "1" {
decimal += math.Pow(-2.0, float64(i-1))
continue
}
if s == "1" {
decimal += math.Pow(2.0, float64(i-1))
continue
}
if s != "0" {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newNumberFormulaArg(decimal)
}
// BITAND function returns the bitwise 'AND' for two supplied integers. The
// syntax of the function is:
//
// BITAND(number1,number2)
func (fn *formulaFuncs) BITAND(argsList *list.List) formulaArg {
return fn.bitwise("BITAND", argsList)
}
// BITLSHIFT function returns a supplied integer, shifted left by a specified
// number of bits. The syntax of the function is:
//
// BITLSHIFT(number1,shift_amount)
func (fn *formulaFuncs) BITLSHIFT(argsList *list.List) formulaArg {
return fn.bitwise("BITLSHIFT", argsList)
}
// BITOR function returns the bitwise 'OR' for two supplied integers. The
// syntax of the function is:
//
// BITOR(number1,number2)
func (fn *formulaFuncs) BITOR(argsList *list.List) formulaArg {
return fn.bitwise("BITOR", argsList)
}
// BITRSHIFT function returns a supplied integer, shifted right by a specified
// number of bits. The syntax of the function is:
//
// BITRSHIFT(number1,shift_amount)
func (fn *formulaFuncs) BITRSHIFT(argsList *list.List) formulaArg {
return fn.bitwise("BITRSHIFT", argsList)
}
// BITXOR function returns the bitwise 'XOR' (exclusive 'OR') for two supplied
// integers. The syntax of the function is:
//
// BITXOR(number1,number2)
func (fn *formulaFuncs) BITXOR(argsList *list.List) formulaArg {
return fn.bitwise("BITXOR", argsList)
}
// bitwise is an implementation of the formula functions BITAND, BITLSHIFT,
// BITOR, BITRSHIFT and BITXOR.
func (fn *formulaFuncs) bitwise(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 numeric arguments", name))
}
num1, num2 := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
if num1.Type != ArgNumber || num2.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
maxVal := math.Pow(2, 48) - 1
if num1.Number < 0 || num1.Number > maxVal || num2.Number < 0 || num2.Number > maxVal {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
bitwiseFuncMap := map[string]func(a, b int) int{
"BITAND": func(a, b int) int { return a & b },
"BITLSHIFT": func(a, b int) int { return a << uint(b) },
"BITOR": func(a, b int) int { return a | b },
"BITRSHIFT": func(a, b int) int { return a >> uint(b) },
"BITXOR": func(a, b int) int { return a ^ b },
}
bitwiseFunc := bitwiseFuncMap[name]
return newNumberFormulaArg(float64(bitwiseFunc(int(num1.Number), int(num2.Number))))
}
// COMPLEX function takes two arguments, representing the real and the
// imaginary coefficients of a complex number, and from these, creates a
// complex number. The syntax of the function is:
//
// COMPLEX(real_num,i_num,[suffix])
func (fn *formulaFuncs) COMPLEX(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX requires at least 2 arguments")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX allows at most 3 arguments")
}
realNum, i, suffix := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber(), "i"
if realNum.Type != ArgNumber {
return realNum
}
if i.Type != ArgNumber {
return i
}
if argsList.Len() == 3 {
if suffix = strings.ToLower(argsList.Back().Value.(formulaArg).Value()); suffix != "i" && suffix != "j" {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
return newStringFormulaArg(cmplx2str(complex(realNum.Number, i.Number), suffix))
}
// cmplx2str replace complex number string characters.
func cmplx2str(num complex128, suffix string) string {
realPart, imagPart := fmt.Sprint(real(num)), fmt.Sprint(imag(num))
isNum, i, decimal := isNumeric(realPart)
if isNum && i > 15 {
realPart = strconv.FormatFloat(decimal, 'G', 15, 64)
}
isNum, i, decimal = isNumeric(imagPart)
if isNum && i > 15 {
imagPart = strconv.FormatFloat(decimal, 'G', 15, 64)
}
c := realPart
if imag(num) > 0 {
c += "+"
}
if imag(num) != 0 {
c += imagPart + "i"
}
c = strings.TrimPrefix(c, "(")
c = strings.TrimPrefix(c, "+0+")
c = strings.TrimPrefix(c, "-0+")
c = strings.TrimSuffix(c, ")")
c = strings.TrimPrefix(c, "0+")
if strings.HasPrefix(c, "0-") {
c = "-" + strings.TrimPrefix(c, "0-")
}
c = strings.TrimPrefix(c, "0+")
c = strings.TrimSuffix(c, "+0i")
c = strings.TrimSuffix(c, "-0i")
c = strings.NewReplacer("+1i", "+i", "-1i", "-i").Replace(c)
c = strings.ReplaceAll(c, "i", suffix)
return c
}
// str2cmplx convert complex number string characters.
func str2cmplx(c string) string {
c = strings.ReplaceAll(c, "j", "i")
if c == "i" {
c = "1i"
}
c = strings.NewReplacer("+i", "+1i", "-i", "-1i").Replace(c)
return c
}
// conversionUnit defined unit info for conversion.
type conversionUnit struct {
group uint8
allowPrefix bool
}
// conversionUnits maps info list for unit conversion, that can be used in
// formula function CONVERT.
var conversionUnits = map[string]conversionUnit{
// weight and mass
"g": {group: categoryWeightAndMass, allowPrefix: true},
"sg": {group: categoryWeightAndMass, allowPrefix: false},
"lbm": {group: categoryWeightAndMass, allowPrefix: false},
"u": {group: categoryWeightAndMass, allowPrefix: true},
"ozm": {group: categoryWeightAndMass, allowPrefix: false},
"grain": {group: categoryWeightAndMass, allowPrefix: false},
"cwt": {group: categoryWeightAndMass, allowPrefix: false},
"shweight": {group: categoryWeightAndMass, allowPrefix: false},
"uk_cwt": {group: categoryWeightAndMass, allowPrefix: false},
"lcwt": {group: categoryWeightAndMass, allowPrefix: false},
"hweight": {group: categoryWeightAndMass, allowPrefix: false},
"stone": {group: categoryWeightAndMass, allowPrefix: false},
"ton": {group: categoryWeightAndMass, allowPrefix: false},
"uk_ton": {group: categoryWeightAndMass, allowPrefix: false},
"LTON": {group: categoryWeightAndMass, allowPrefix: false},
"brton": {group: categoryWeightAndMass, allowPrefix: false},
// distance
"m": {group: categoryDistance, allowPrefix: true},
"mi": {group: categoryDistance, allowPrefix: false},
"Nmi": {group: categoryDistance, allowPrefix: false},
"in": {group: categoryDistance, allowPrefix: false},
"ft": {group: categoryDistance, allowPrefix: false},
"yd": {group: categoryDistance, allowPrefix: false},
"ang": {group: categoryDistance, allowPrefix: true},
"ell": {group: categoryDistance, allowPrefix: false},
"ly": {group: categoryDistance, allowPrefix: false},
"parsec": {group: categoryDistance, allowPrefix: false},
"pc": {group: categoryDistance, allowPrefix: false},
"Pica": {group: categoryDistance, allowPrefix: false},
"Picapt": {group: categoryDistance, allowPrefix: false},
"pica": {group: categoryDistance, allowPrefix: false},
"survey_mi": {group: categoryDistance, allowPrefix: false},
// time
"yr": {group: categoryTime, allowPrefix: false},
"day": {group: categoryTime, allowPrefix: false},
"d": {group: categoryTime, allowPrefix: false},
"hr": {group: categoryTime, allowPrefix: false},
"mn": {group: categoryTime, allowPrefix: false},
"min": {group: categoryTime, allowPrefix: false},
"sec": {group: categoryTime, allowPrefix: true},
"s": {group: categoryTime, allowPrefix: true},
// pressure
"Pa": {group: categoryPressure, allowPrefix: true},
"p": {group: categoryPressure, allowPrefix: true},
"atm": {group: categoryPressure, allowPrefix: true},
"at": {group: categoryPressure, allowPrefix: true},
"mmHg": {group: categoryPressure, allowPrefix: true},
"psi": {group: categoryPressure, allowPrefix: true},
"Torr": {group: categoryPressure, allowPrefix: true},
// force
"N": {group: categoryForce, allowPrefix: true},
"dyn": {group: categoryForce, allowPrefix: true},
"dy": {group: categoryForce, allowPrefix: true},
"lbf": {group: categoryForce, allowPrefix: false},
"pond": {group: categoryForce, allowPrefix: true},
// energy
"J": {group: categoryEnergy, allowPrefix: true},
"e": {group: categoryEnergy, allowPrefix: true},
"c": {group: categoryEnergy, allowPrefix: true},
"cal": {group: categoryEnergy, allowPrefix: true},
"eV": {group: categoryEnergy, allowPrefix: true},
"ev": {group: categoryEnergy, allowPrefix: true},
"HPh": {group: categoryEnergy, allowPrefix: false},
"hh": {group: categoryEnergy, allowPrefix: false},
"Wh": {group: categoryEnergy, allowPrefix: true},
"wh": {group: categoryEnergy, allowPrefix: true},
"flb": {group: categoryEnergy, allowPrefix: false},
"BTU": {group: categoryEnergy, allowPrefix: false},
"btu": {group: categoryEnergy, allowPrefix: false},
// power
"HP": {group: categoryPower, allowPrefix: false},
"h": {group: categoryPower, allowPrefix: false},
"W": {group: categoryPower, allowPrefix: true},
"w": {group: categoryPower, allowPrefix: true},
"PS": {group: categoryPower, allowPrefix: false},
"T": {group: categoryMagnetism, allowPrefix: true},
"ga": {group: categoryMagnetism, allowPrefix: true},
// temperature
"C": {group: categoryTemperature, allowPrefix: false},
"cel": {group: categoryTemperature, allowPrefix: false},
"F": {group: categoryTemperature, allowPrefix: false},
"fah": {group: categoryTemperature, allowPrefix: false},
"K": {group: categoryTemperature, allowPrefix: false},
"kel": {group: categoryTemperature, allowPrefix: false},
"Rank": {group: categoryTemperature, allowPrefix: false},
"Reau": {group: categoryTemperature, allowPrefix: false},
// volume
"l": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"L": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"lt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"tsp": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"tspm": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"tbs": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"oz": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"cup": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"us_pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"uk_pt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"qt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"uk_qt": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"gal": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"uk_gal": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"ang3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"ang^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"barrel": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"bushel": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"in3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"in^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"ft3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"ft^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"ly3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"ly^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"m3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"m^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: true},
"mi3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"mi^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"yd3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"yd^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Nmi3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Nmi^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Pica3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Pica^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Picapt3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"Picapt^3": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"GRT": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"regton": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
"MTON": {group: categoryVolumeAndLiquidMeasure, allowPrefix: false},
// area
"ha": {group: categoryArea, allowPrefix: true},
"uk_acre": {group: categoryArea, allowPrefix: false},
"us_acre": {group: categoryArea, allowPrefix: false},
"ang2": {group: categoryArea, allowPrefix: true},
"ang^2": {group: categoryArea, allowPrefix: true},
"ar": {group: categoryArea, allowPrefix: true},
"ft2": {group: categoryArea, allowPrefix: false},
"ft^2": {group: categoryArea, allowPrefix: false},
"in2": {group: categoryArea, allowPrefix: false},
"in^2": {group: categoryArea, allowPrefix: false},
"ly2": {group: categoryArea, allowPrefix: false},
"ly^2": {group: categoryArea, allowPrefix: false},
"m2": {group: categoryArea, allowPrefix: true},
"m^2": {group: categoryArea, allowPrefix: true},
"Morgen": {group: categoryArea, allowPrefix: false},
"mi2": {group: categoryArea, allowPrefix: false},
"mi^2": {group: categoryArea, allowPrefix: false},
"Nmi2": {group: categoryArea, allowPrefix: false},
"Nmi^2": {group: categoryArea, allowPrefix: false},
"Pica2": {group: categoryArea, allowPrefix: false},
"Pica^2": {group: categoryArea, allowPrefix: false},
"Picapt2": {group: categoryArea, allowPrefix: false},
"Picapt^2": {group: categoryArea, allowPrefix: false},
"yd2": {group: categoryArea, allowPrefix: false},
"yd^2": {group: categoryArea, allowPrefix: false},
// information
"byte": {group: categoryInformation, allowPrefix: true},
"bit": {group: categoryInformation, allowPrefix: true},
// speed
"m/s": {group: categorySpeed, allowPrefix: true},
"m/sec": {group: categorySpeed, allowPrefix: true},
"m/h": {group: categorySpeed, allowPrefix: true},
"m/hr": {group: categorySpeed, allowPrefix: true},
"mph": {group: categorySpeed, allowPrefix: false},
"admkn": {group: categorySpeed, allowPrefix: false},
"kn": {group: categorySpeed, allowPrefix: false},
}
// unitConversions maps details of the Units of measure conversion factors,
// organised by group.
var unitConversions = map[byte]map[string]float64{
// conversion uses gram (g) as an intermediate unit
categoryWeightAndMass: {
"g": 1,
"sg": 6.85217658567918e-05,
"lbm": 2.20462262184878e-03,
"u": 6.02214179421676e+23,
"ozm": 3.52739619495804e-02,
"grain": 1.54323583529414e+01,
"cwt": 2.20462262184878e-05,
"shweight": 2.20462262184878e-05,
"uk_cwt": 1.96841305522212e-05,
"lcwt": 1.96841305522212e-05,
"hweight": 1.96841305522212e-05,
"stone": 1.57473044417770e-04,
"ton": 1.10231131092439e-06,
"uk_ton": 9.84206527611061e-07,
"LTON": 9.84206527611061e-07,
"brton": 9.84206527611061e-07,
},
// conversion uses meter (m) as an intermediate unit
categoryDistance: {
"m": 1,
"mi": 6.21371192237334e-04,
"Nmi": 5.39956803455724e-04,
"in": 3.93700787401575e+01,
"ft": 3.28083989501312e+00,
"yd": 1.09361329833771e+00,
"ang": 1.0e+10,
"ell": 8.74890638670166e-01,
"ly": 1.05700083402462e-16,
"parsec": 3.24077928966473e-17,
"pc": 3.24077928966473e-17,
"Pica": 2.83464566929134e+03,
"Picapt": 2.83464566929134e+03,
"pica": 2.36220472440945e+02,
"survey_mi": 6.21369949494950e-04,
},
// conversion uses second (s) as an intermediate unit
categoryTime: {
"yr": 3.16880878140289e-08,
"day": 1.15740740740741e-05,
"d": 1.15740740740741e-05,
"hr": 2.77777777777778e-04,
"mn": 1.66666666666667e-02,
"min": 1.66666666666667e-02,
"sec": 1,
"s": 1,
},
// conversion uses Pascal (Pa) as an intermediate unit
categoryPressure: {
"Pa": 1,
"p": 1,
"atm": 9.86923266716013e-06,
"at": 9.86923266716013e-06,
"mmHg": 7.50063755419211e-03,
"psi": 1.45037737730209e-04,
"Torr": 7.50061682704170e-03,
},
// conversion uses Newton (N) as an intermediate unit
categoryForce: {
"N": 1,
"dyn": 1.0e+5,
"dy": 1.0e+5,
"lbf": 2.24808923655339e-01,
"pond": 1.01971621297793e+02,
},
// conversion uses Joule (J) as an intermediate unit
categoryEnergy: {
"J": 1,
"e": 9.99999519343231e+06,
"c": 2.39006249473467e-01,
"cal": 2.38846190642017e-01,
"eV": 6.24145700000000e+18,
"ev": 6.24145700000000e+18,
"HPh": 3.72506430801000e-07,
"hh": 3.72506430801000e-07,
"Wh": 2.77777916238711e-04,
"wh": 2.77777916238711e-04,
"flb": 2.37304222192651e+01,
"BTU": 9.47815067349015e-04,
"btu": 9.47815067349015e-04,
},
// conversion uses Horsepower (HP) as an intermediate unit
categoryPower: {
"HP": 1,
"h": 1,
"W": 7.45699871582270e+02,
"w": 7.45699871582270e+02,
"PS": 1.01386966542400e+00,
},
// conversion uses Tesla (T) as an intermediate unit
categoryMagnetism: {
"T": 1,
"ga": 10000,
},
// conversion uses litre (l) as an intermediate unit
categoryVolumeAndLiquidMeasure: {
"l": 1,
"L": 1,
"lt": 1,
"tsp": 2.02884136211058e+02,
"tspm": 2.0e+02,
"tbs": 6.76280454036860e+01,
"oz": 3.38140227018430e+01,
"cup": 4.22675283773038e+00,
"pt": 2.11337641886519e+00,
"us_pt": 2.11337641886519e+00,
"uk_pt": 1.75975398639270e+00,
"qt": 1.05668820943259e+00,
"uk_qt": 8.79876993196351e-01,
"gal": 2.64172052358148e-01,
"uk_gal": 2.19969248299088e-01,
"ang3": 1.0e+27,
"ang^3": 1.0e+27,
"barrel": 6.28981077043211e-03,
"bushel": 2.83775932584017e-02,
"in3": 6.10237440947323e+01,
"in^3": 6.10237440947323e+01,
"ft3": 3.53146667214886e-02,
"ft^3": 3.53146667214886e-02,
"ly3": 1.18093498844171e-51,
"ly^3": 1.18093498844171e-51,
"m3": 1.0e-03,
"m^3": 1.0e-03,
"mi3": 2.39912758578928e-13,
"mi^3": 2.39912758578928e-13,
"yd3": 1.30795061931439e-03,
"yd^3": 1.30795061931439e-03,
"Nmi3": 1.57426214685811e-13,
"Nmi^3": 1.57426214685811e-13,
"Pica3": 2.27769904358706e+07,
"Pica^3": 2.27769904358706e+07,
"Picapt3": 2.27769904358706e+07,
"Picapt^3": 2.27769904358706e+07,
"GRT": 3.53146667214886e-04,
"regton": 3.53146667214886e-04,
"MTON": 8.82866668037215e-04,
},
// conversion uses hectare (ha) as an intermediate unit
categoryArea: {
"ha": 1,
"uk_acre": 2.47105381467165e+00,
"us_acre": 2.47104393046628e+00,
"ang2": 1.0e+24,
"ang^2": 1.0e+24,
"ar": 1.0e+02,
"ft2": 1.07639104167097e+05,
"ft^2": 1.07639104167097e+05,
"in2": 1.55000310000620e+07,
"in^2": 1.55000310000620e+07,
"ly2": 1.11725076312873e-28,
"ly^2": 1.11725076312873e-28,
"m2": 1.0e+04,
"m^2": 1.0e+04,
"Morgen": 4.0e+00,
"mi2": 3.86102158542446e-03,
"mi^2": 3.86102158542446e-03,
"Nmi2": 2.91553349598123e-03,
"Nmi^2": 2.91553349598123e-03,
"Pica2": 8.03521607043214e+10,
"Pica^2": 8.03521607043214e+10,
"Picapt2": 8.03521607043214e+10,
"Picapt^2": 8.03521607043214e+10,
"yd2": 1.19599004630108e+04,
"yd^2": 1.19599004630108e+04,
},
// conversion uses bit (bit) as an intermediate unit
categoryInformation: {
"bit": 1,
"byte": 0.125,
},
// conversion uses Meters per Second (m/s) as an intermediate unit
categorySpeed: {
"m/s": 1,
"m/sec": 1,
"m/h": 3.60e+03,
"m/hr": 3.60e+03,
"mph": 2.23693629205440e+00,
"admkn": 1.94260256941567e+00,
"kn": 1.94384449244060e+00,
},
}
// conversionMultipliers maps details of the Multiplier prefixes that can be
// used with Units of Measure in CONVERT.
var conversionMultipliers = map[string]float64{
"Y": 1e24,
"Z": 1e21,
"E": 1e18,
"P": 1e15,
"T": 1e12,
"G": 1e9,
"M": 1e6,
"k": 1e3,
"h": 1e2,
"e": 1e1,
"da": 1e1,
"d": 1e-1,
"c": 1e-2,
"m": 1e-3,
"u": 1e-6,
"n": 1e-9,
"p": 1e-12,
"f": 1e-15,
"a": 1e-18,
"z": 1e-21,
"y": 1e-24,
"Yi": math.Pow(2, 80),
"Zi": math.Pow(2, 70),
"Ei": math.Pow(2, 60),
"Pi": math.Pow(2, 50),
"Ti": math.Pow(2, 40),
"Gi": math.Pow(2, 30),
"Mi": math.Pow(2, 20),
"ki": math.Pow(2, 10),
}
// getUnitDetails check and returns the unit of measure details.
func getUnitDetails(uom string) (unit string, catgory byte, res float64, ok bool) {
if len(uom) == 0 {
ok = false
return
}
if unit, ok := conversionUnits[uom]; ok {
return uom, unit.group, 1, ok
}
// 1 character standard metric multiplier prefixes
multiplierType := uom[:1]
uom = uom[1:]
conversionUnit, ok1 := conversionUnits[uom]
multiplier, ok2 := conversionMultipliers[multiplierType]
if ok1 && ok2 {
if !conversionUnit.allowPrefix {
ok = false
return
}
unitCategory := conversionUnit.group
return uom, unitCategory, multiplier, true
}
// 2 character standard and binary metric multiplier prefixes
if len(uom) > 0 {
multiplierType += uom[:1]
uom = uom[1:]
}
conversionUnit, ok1 = conversionUnits[uom]
multiplier, ok2 = conversionMultipliers[multiplierType]
if ok1 && ok2 {
if !conversionUnit.allowPrefix {
ok = false
return
}
unitCategory := conversionUnit.group
return uom, unitCategory, multiplier, true
}
ok = false
return
}
// resolveTemperatureSynonyms returns unit of measure according to a given
// temperature synonyms.
func resolveTemperatureSynonyms(uom string) string {
switch uom {
case "fah":
return "F"
case "cel":
return "C"
case "kel":
return "K"
}
return uom
}
// convertTemperature returns converted temperature by a given unit of measure.
func convertTemperature(fromUOM, toUOM string, value float64) float64 {
fromUOM = resolveTemperatureSynonyms(fromUOM)
toUOM = resolveTemperatureSynonyms(toUOM)
if fromUOM == toUOM {
return value
}
// convert to Kelvin
switch fromUOM {
case "F":
value = (value-32)/1.8 + 273.15
case "C":
value += 273.15
case "Rank":
value /= 1.8
case "Reau":
value = value*1.25 + 273.15
}
// convert from Kelvin
switch toUOM {
case "F":
value = (value-273.15)*1.8 + 32
case "C":
value -= 273.15
case "Rank":
value *= 1.8
case "Reau":
value = (value - 273.15) * 0.8
}
return value
}
// CONVERT function converts a number from one unit type (e.g. Yards) to
// another unit type (e.g. Meters). The syntax of the function is:
//
// CONVERT(number,from_unit,to_unit)
func (fn *formulaFuncs) CONVERT(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "CONVERT requires 3 arguments")
}
num := argsList.Front().Value.(formulaArg).ToNumber()
if num.Type != ArgNumber {
return num
}
fromUOM, fromCategory, fromMultiplier, ok1 := getUnitDetails(argsList.Front().Next().Value.(formulaArg).Value())
toUOM, toCategory, toMultiplier, ok2 := getUnitDetails(argsList.Back().Value.(formulaArg).Value())
if !ok1 || !ok2 || fromCategory != toCategory {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
val := num.Number * fromMultiplier
if fromUOM == toUOM && fromMultiplier == toMultiplier {
return newNumberFormulaArg(val / fromMultiplier)
} else if fromUOM == toUOM {
return newNumberFormulaArg(val / toMultiplier)
} else if fromCategory == categoryTemperature {
return newNumberFormulaArg(convertTemperature(fromUOM, toUOM, val))
}
fromConversion := unitConversions[fromCategory][fromUOM]
toConversion := unitConversions[fromCategory][toUOM]
baseValue := val * (1 / fromConversion)
return newNumberFormulaArg((baseValue * toConversion) / toMultiplier)
}
// DEC2BIN function converts a decimal number into a Binary (Base 2) number.
// The syntax of the function is:
//
// DEC2BIN(number,[places])
func (fn *formulaFuncs) DEC2BIN(argsList *list.List) formulaArg {
return fn.dec2x("DEC2BIN", argsList)
}
// DEC2HEX function converts a decimal number into a Hexadecimal (Base 16)
// number. The syntax of the function is:
//
// DEC2HEX(number,[places])
func (fn *formulaFuncs) DEC2HEX(argsList *list.List) formulaArg {
return fn.dec2x("DEC2HEX", argsList)
}
// DEC2OCT function converts a decimal number into an Octal (Base 8) number.
// The syntax of the function is:
//
// DEC2OCT(number,[places])
func (fn *formulaFuncs) DEC2OCT(argsList *list.List) formulaArg {
return fn.dec2x("DEC2OCT", argsList)
}
// dec2x is an implementation of the formula functions DEC2BIN, DEC2HEX and
// DEC2OCT.
func (fn *formulaFuncs) dec2x(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
}
decimal := argsList.Front().Value.(formulaArg).ToNumber()
if decimal.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, decimal.Error)
}
maxLimitMap := map[string]float64{
"DEC2BIN": 511,
"HEX2BIN": 511,
"OCT2BIN": 511,
"BIN2HEX": 549755813887,
"DEC2HEX": 549755813887,
"OCT2HEX": 549755813887,
"BIN2OCT": 536870911,
"DEC2OCT": 536870911,
"HEX2OCT": 536870911,
}
minLimitMap := map[string]float64{
"DEC2BIN": -512,
"HEX2BIN": -512,
"OCT2BIN": -512,
"BIN2HEX": -549755813888,
"DEC2HEX": -549755813888,
"OCT2HEX": -549755813888,
"BIN2OCT": -536870912,
"DEC2OCT": -536870912,
"HEX2OCT": -536870912,
}
baseMap := map[string]int{
"DEC2BIN": 2,
"HEX2BIN": 2,
"OCT2BIN": 2,
"BIN2HEX": 16,
"DEC2HEX": 16,
"OCT2HEX": 16,
"BIN2OCT": 8,
"DEC2OCT": 8,
"HEX2OCT": 8,
}
maxLimit, minLimit := maxLimitMap[name], minLimitMap[name]
base := baseMap[name]
if decimal.Number < minLimit || decimal.Number > maxLimit {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
n := int64(decimal.Number)
binary := strconv.FormatUint(*(*uint64)(unsafe.Pointer(&n)), base)
if argsList.Len() == 2 {
places := argsList.Back().Value.(formulaArg).ToNumber()
if places.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, places.Error)
}
binaryPlaces := len(binary)
if places.Number < 0 || places.Number > 10 || binaryPlaces > int(places.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%s%s", strings.Repeat("0", int(places.Number)-binaryPlaces), binary)))
}
if decimal.Number < 0 && len(binary) > 10 {
return newStringFormulaArg(strings.ToUpper(binary[len(binary)-10:]))
}
return newStringFormulaArg(strings.ToUpper(binary))
}
// DELTA function tests two numbers for equality and returns the Kronecker
// Delta. i.e. the function returns 1 if the two supplied numbers are equal
// and 0 otherwise. The syntax of the function is:
//
// DELTA(number1,[number2])
func (fn *formulaFuncs) DELTA(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DELTA requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DELTA allows at most 2 arguments")
}
number1 := argsList.Front().Value.(formulaArg).ToNumber()
if number1.Type != ArgNumber {
return number1
}
number2 := newNumberFormulaArg(0)
if argsList.Len() == 2 {
if number2 = argsList.Back().Value.(formulaArg).ToNumber(); number2.Type != ArgNumber {
return number2
}
}
return newBoolFormulaArg(number1.Number == number2.Number).ToNumber()
}
// ERF function calculates the Error Function, integrated between two supplied
// limits. The syntax of the function is:
//
// ERF(lower_limit,[upper_limit])
func (fn *formulaFuncs) ERF(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ERF requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ERF allows at most 2 arguments")
}
lower := argsList.Front().Value.(formulaArg).ToNumber()
if lower.Type != ArgNumber {
return lower
}
if argsList.Len() == 2 {
upper := argsList.Back().Value.(formulaArg).ToNumber()
if upper.Type != ArgNumber {
return upper
}
return newNumberFormulaArg(math.Erf(upper.Number) - math.Erf(lower.Number))
}
return newNumberFormulaArg(math.Erf(lower.Number))
}
// ERFdotPRECISE function calculates the Error Function, integrated between a
// supplied lower or upper limit and 0. The syntax of the function is:
//
// ERF.PRECISE(x)
func (fn *formulaFuncs) ERFdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ERF.PRECISE requires 1 argument")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
return newNumberFormulaArg(math.Erf(x.Number))
}
// erfc is an implementation of the formula functions ERFC and ERFC.PRECISE.
func (fn *formulaFuncs) erfc(name string, argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name))
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
return newNumberFormulaArg(math.Erfc(x.Number))
}
// ERFC function calculates the Complementary Error Function, integrated
// between a supplied lower limit and infinity. The syntax of the function
// is:
//
// ERFC(x)
func (fn *formulaFuncs) ERFC(argsList *list.List) formulaArg {
return fn.erfc("ERFC", argsList)
}
// ERFCdotPRECISE function calculates the Complementary Error Function,
// integrated between a supplied lower limit and infinity. The syntax of the
// function is:
//
// ERFC(x)
func (fn *formulaFuncs) ERFCdotPRECISE(argsList *list.List) formulaArg {
return fn.erfc("ERFC.PRECISE", argsList)
}
// GESTEP unction tests whether a supplied number is greater than a supplied
// step size and returns. The syntax of the function is:
//
// GESTEP(number,[step])
func (fn *formulaFuncs) GESTEP(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GESTEP requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "GESTEP allows at most 2 arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type != ArgNumber {
return number
}
step := newNumberFormulaArg(0)
if argsList.Len() == 2 {
if step = argsList.Back().Value.(formulaArg).ToNumber(); step.Type != ArgNumber {
return step
}
}
return newBoolFormulaArg(number.Number >= step.Number).ToNumber()
}
// HEX2BIN function converts a Hexadecimal (Base 16) number into a Binary
// (Base 2) number. The syntax of the function is:
//
// HEX2BIN(number,[places])
func (fn *formulaFuncs) HEX2BIN(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN allows at most 2 arguments")
}
decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New()
if decimal.Type != ArgNumber {
return decimal
}
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("HEX2BIN", newList)
}
// HEX2DEC function converts a hexadecimal (a base-16 number) into a decimal
// number. The syntax of the function is:
//
// HEX2DEC(number)
func (fn *formulaFuncs) HEX2DEC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HEX2DEC requires 1 numeric argument")
}
return fn.hex2dec(argsList.Front().Value.(formulaArg).Value())
}
// HEX2OCT function converts a Hexadecimal (Base 16) number into an Octal
// (Base 8) number. The syntax of the function is:
//
// HEX2OCT(number,[places])
func (fn *formulaFuncs) HEX2OCT(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT allows at most 2 arguments")
}
decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New()
if decimal.Type != ArgNumber {
return decimal
}
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("HEX2OCT", newList)
}
// hex2dec is an implementation of the formula function HEX2DEC.
func (fn *formulaFuncs) hex2dec(number string) formulaArg {
decimal, length := 0.0, len(number)
for i := length; i > 0; i-- {
num, err := strconv.ParseInt(string(number[length-i]), 16, 64)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
if i == 10 && string(number[length-i]) == "F" {
decimal += math.Pow(-16.0, float64(i-1))
continue
}
decimal += float64(num) * math.Pow(16.0, float64(i-1))
}
return newNumberFormulaArg(decimal)
}
// IMABS function returns the absolute value (the modulus) of a complex
// number. The syntax of the function is:
//
// IMABS(inumber)
func (fn *formulaFuncs) IMABS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMABS requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newNumberFormulaArg(cmplx.Abs(inumber))
}
// IMAGINARY function returns the imaginary coefficient of a supplied complex
// number. The syntax of the function is:
//
// IMAGINARY(inumber)
func (fn *formulaFuncs) IMAGINARY(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMAGINARY requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newNumberFormulaArg(imag(inumber))
}
// IMARGUMENT function returns the phase (also called the argument) of a
// supplied complex number. The syntax of the function is:
//
// IMARGUMENT(inumber)
func (fn *formulaFuncs) IMARGUMENT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMARGUMENT requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newNumberFormulaArg(cmplx.Phase(inumber))
}
// IMCONJUGATE function returns the complex conjugate of a supplied complex
// number. The syntax of the function is:
//
// IMCONJUGATE(inumber)
func (fn *formulaFuncs) IMCONJUGATE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCONJUGATE requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Conj(inumber), value[len(value)-1:]))
}
// IMCOS function returns the cosine of a supplied complex number. The syntax
// of the function is:
//
// IMCOS(inumber)
func (fn *formulaFuncs) IMCOS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCOS requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Cos(inumber), value[len(value)-1:]))
}
// IMCOSH function returns the hyperbolic cosine of a supplied complex number. The syntax
// of the function is:
//
// IMCOSH(inumber)
func (fn *formulaFuncs) IMCOSH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCOSH requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Cosh(inumber), value[len(value)-1:]))
}
// IMCOT function returns the cotangent of a supplied complex number. The syntax
// of the function is:
//
// IMCOT(inumber)
func (fn *formulaFuncs) IMCOT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCOT requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Cot(inumber), value[len(value)-1:]))
}
// IMCSC function returns the cosecant of a supplied complex number. The syntax
// of the function is:
//
// IMCSC(inumber)
func (fn *formulaFuncs) IMCSC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCSC requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := 1 / cmplx.Sin(inumber)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMCSCH function returns the hyperbolic cosecant of a supplied complex
// number. The syntax of the function is:
//
// IMCSCH(inumber)
func (fn *formulaFuncs) IMCSCH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMCSCH requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := 1 / cmplx.Sinh(inumber)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMDIV function calculates the quotient of two complex numbers (i.e. divides
// one complex number by another). The syntax of the function is:
//
// IMDIV(inumber1,inumber2)
func (fn *formulaFuncs) IMDIV(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IMDIV requires 2 arguments")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber1, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
inumber2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := inumber1 / inumber2
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMEXP function returns the exponential of a supplied complex number. The
// syntax of the function is:
//
// IMEXP(inumber)
func (fn *formulaFuncs) IMEXP(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMEXP requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Exp(inumber), value[len(value)-1:]))
}
// IMLN function returns the natural logarithm of a supplied complex number.
// The syntax of the function is:
//
// IMLN(inumber)
func (fn *formulaFuncs) IMLN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMLN requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := cmplx.Log(inumber)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMLOG10 function returns the common (base 10) logarithm of a supplied
// complex number. The syntax of the function is:
//
// IMLOG10(inumber)
func (fn *formulaFuncs) IMLOG10(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMLOG10 requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := cmplx.Log10(inumber)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMLOG2 function calculates the base 2 logarithm of a supplied complex
// number. The syntax of the function is:
//
// IMLOG2(inumber)
func (fn *formulaFuncs) IMLOG2(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMLOG2 requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
num := cmplx.Log(inumber)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num/cmplx.Log(2), value[len(value)-1:]))
}
// IMPOWER function returns a supplied complex number, raised to a given
// power. The syntax of the function is:
//
// IMPOWER(inumber,number)
func (fn *formulaFuncs) IMPOWER(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IMPOWER requires 2 arguments")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
number, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
if inumber == 0 && number == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
num := cmplx.Pow(inumber, number)
if cmplx.IsInf(num) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newStringFormulaArg(cmplx2str(num, value[len(value)-1:]))
}
// IMPRODUCT function calculates the product of two or more complex numbers.
// The syntax of the function is:
//
// IMPRODUCT(number1,[number2],...)
func (fn *formulaFuncs) IMPRODUCT(argsList *list.List) formulaArg {
product := complex128(1)
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
if token.Value() == "" {
continue
}
val, err := strconv.ParseComplex(str2cmplx(token.Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
product = product * val
case ArgNumber:
product = product * complex(token.Number, 0)
case ArgMatrix:
for _, row := range token.Matrix {
for _, value := range row {
if value.Value() == "" {
continue
}
val, err := strconv.ParseComplex(str2cmplx(value.Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
product = product * val
}
}
}
}
return newStringFormulaArg(cmplx2str(product, "i"))
}
// IMREAL function returns the real coefficient of a supplied complex number.
// The syntax of the function is:
//
// IMREAL(inumber)
func (fn *formulaFuncs) IMREAL(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMREAL requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(fmt.Sprint(real(inumber)))
}
// IMSEC function returns the secant of a supplied complex number. The syntax
// of the function is:
//
// IMSEC(inumber)
func (fn *formulaFuncs) IMSEC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSEC requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(1/cmplx.Cos(inumber), value[len(value)-1:]))
}
// IMSECH function returns the hyperbolic secant of a supplied complex number.
// The syntax of the function is:
//
// IMSECH(inumber)
func (fn *formulaFuncs) IMSECH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSECH requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(1/cmplx.Cosh(inumber), value[len(value)-1:]))
}
// IMSIN function returns the Sine of a supplied complex number. The syntax of
// the function is:
//
// IMSIN(inumber)
func (fn *formulaFuncs) IMSIN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSIN requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Sin(inumber), value[len(value)-1:]))
}
// IMSINH function returns the hyperbolic sine of a supplied complex number.
// The syntax of the function is:
//
// IMSINH(inumber)
func (fn *formulaFuncs) IMSINH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSINH requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Sinh(inumber), value[len(value)-1:]))
}
// IMSQRT function returns the square root of a supplied complex number. The
// syntax of the function is:
//
// IMSQRT(inumber)
func (fn *formulaFuncs) IMSQRT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSQRT requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Sqrt(inumber), value[len(value)-1:]))
}
// IMSUB function calculates the difference between two complex numbers
// (i.e. subtracts one complex number from another). The syntax of the
// function is:
//
// IMSUB(inumber1,inumber2)
func (fn *formulaFuncs) IMSUB(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSUB requires 2 arguments")
}
i1, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
i2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(i1-i2, "i"))
}
// IMSUM function calculates the sum of two or more complex numbers. The
// syntax of the function is:
//
// IMSUM(inumber1,inumber2,...)
func (fn *formulaFuncs) IMSUM(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMSUM requires at least 1 argument")
}
var result complex128
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
num, err := strconv.ParseComplex(str2cmplx(token.Value()), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
result += num
}
return newStringFormulaArg(cmplx2str(result, "i"))
}
// IMTAN function returns the tangent of a supplied complex number. The syntax
// of the function is:
//
// IMTAN(inumber)
func (fn *formulaFuncs) IMTAN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IMTAN requires 1 argument")
}
value := argsList.Front().Value.(formulaArg).Value()
inumber, err := strconv.ParseComplex(str2cmplx(value), 128)
if err != nil {
return newErrorFormulaArg(formulaErrorNUM, err.Error())
}
return newStringFormulaArg(cmplx2str(cmplx.Tan(inumber), value[len(value)-1:]))
}
// OCT2BIN function converts an Octal (Base 8) number into a Binary (Base 2)
// number. The syntax of the function is:
//
// OCT2BIN(number,[places])
func (fn *formulaFuncs) OCT2BIN(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN allows at most 2 arguments")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
decimal, newList := fn.oct2dec(token.Value()), list.New()
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("OCT2BIN", newList)
}
// OCT2DEC function converts an Octal (a base-8 number) into a decimal number.
// The syntax of the function is:
//
// OCT2DEC(number)
func (fn *formulaFuncs) OCT2DEC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "OCT2DEC requires 1 numeric argument")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
return fn.oct2dec(token.Value())
}
// OCT2HEX function converts an Octal (Base 8) number into a Hexadecimal
// (Base 16) number. The syntax of the function is:
//
// OCT2HEX(number,[places])
func (fn *formulaFuncs) OCT2HEX(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX allows at most 2 arguments")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
decimal, newList := fn.oct2dec(token.Value()), list.New()
newList.PushBack(decimal)
if argsList.Len() == 2 {
newList.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.dec2x("OCT2HEX", newList)
}
// oct2dec is an implementation of the formula function OCT2DEC.
func (fn *formulaFuncs) oct2dec(number string) formulaArg {
decimal, length := 0.0, len(number)
for i := length; i > 0; i-- {
num, _ := strconv.Atoi(string(number[length-i]))
if i == 10 && string(number[length-i]) == "7" {
decimal += math.Pow(-8.0, float64(i-1))
continue
}
decimal += float64(num) * math.Pow(8.0, float64(i-1))
}
return newNumberFormulaArg(decimal)
}
// Math and Trigonometric Functions
// ABS function returns the absolute value of any supplied number. The syntax
// of the function is:
//
// ABS(number)
func (fn *formulaFuncs) ABS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ABS requires 1 numeric argument")
}
arg := fn.implicitIntersect(argsList.Front().Value.(formulaArg)).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Abs(arg.Number))
}
// ACOS function calculates the arccosine (i.e. the inverse cosine) of a given
// number, and returns an angle, in radians, between 0 and π. The syntax of
// the function is:
//
// ACOS(number)
func (fn *formulaFuncs) ACOS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ACOS requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Acos(arg.Number))
}
// ACOSH function calculates the inverse hyperbolic cosine of a supplied number.
// of the function is:
//
// ACOSH(number)
func (fn *formulaFuncs) ACOSH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ACOSH requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Acosh(arg.Number))
}
// ACOT function calculates the arccotangent (i.e. the inverse cotangent) of a
// given number, and returns an angle, in radians, between 0 and π. The syntax
// of the function is:
//
// ACOT(number)
func (fn *formulaFuncs) ACOT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ACOT requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Pi/2 - math.Atan(arg.Number))
}
// ACOTH function calculates the hyperbolic arccotangent (coth) of a supplied
// value. The syntax of the function is:
//
// ACOTH(number)
func (fn *formulaFuncs) ACOTH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ACOTH requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Atanh(1 / arg.Number))
}
// AGGREGATE function returns the result of a specified operation or function,
// applied to a list or database of values. The syntax of the function is:
//
// AGGREGATE(function_num,options,ref1,[ref2],...)
func (fn *formulaFuncs) AGGREGATE(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE requires at least 3 arguments")
}
var fnNum, opts formulaArg
if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber {
return fnNum
}
subFn, ok := map[int]func(argsList *list.List) formulaArg{
1: fn.AVERAGE,
2: fn.COUNT,
3: fn.COUNTA,
4: fn.MAX,
5: fn.MIN,
6: fn.PRODUCT,
7: fn.STDEVdotS,
8: fn.STDEVdotP,
9: fn.SUM,
10: fn.VARdotS,
11: fn.VARdotP,
12: fn.MEDIAN,
13: fn.MODEdotSNGL,
14: fn.LARGE,
15: fn.SMALL,
16: fn.PERCENTILEdotINC,
17: fn.QUARTILEdotINC,
18: fn.PERCENTILEdotEXC,
19: fn.QUARTILEdotEXC,
}[int(fnNum.Number)]
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid function_num")
}
if opts = argsList.Front().Next().Value.(formulaArg).ToNumber(); opts.Type != ArgNumber {
return opts
}
// TODO: apply option argument values to be ignored during the calculation
if int(opts.Number) < 0 || int(opts.Number) > 7 {
return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid options")
}
subArgList := list.New().Init()
for arg := argsList.Front().Next().Next(); arg != nil; arg = arg.Next() {
subArgList.PushBack(arg.Value.(formulaArg))
}
return subFn(subArgList)
}
// ARABIC function converts a Roman numeral into an Arabic numeral. The syntax
// of the function is:
//
// ARABIC(text)
func (fn *formulaFuncs) ARABIC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument")
}
text := argsList.Front().Value.(formulaArg).Value()
if countUTF16String(text) > MaxFieldLength {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
text = strings.ToUpper(text)
number, actualStart, index, isNegative := 0, 0, len(text)-1, false
startIndex, subtractNumber, currentPartValue, currentCharValue, prevCharValue := 0, 0, 0, 0, -1
for index >= 0 && text[index] == ' ' {
index--
}
for actualStart <= index && text[actualStart] == ' ' {
actualStart++
}
if actualStart <= index && text[actualStart] == '-' {
isNegative = true
actualStart++
}
charMap := map[rune]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
for index >= actualStart {
startIndex = index
startChar := text[startIndex]
index--
for index >= actualStart && (text[index]|' ') == startChar {
index--
}
currentCharValue = charMap[rune(startChar)]
currentPartValue = (startIndex - index) * currentCharValue
if currentCharValue >= prevCharValue {
number += currentPartValue - subtractNumber
prevCharValue = currentCharValue
subtractNumber = 0
continue
}
subtractNumber += currentPartValue
}
if subtractNumber != 0 {
number -= subtractNumber
}
if isNegative {
number = -number
}
return newNumberFormulaArg(float64(number))
}
// ASIN function calculates the arcsine (i.e. the inverse sine) of a given
// number, and returns an angle, in radians, between -π/2 and π/2. The syntax
// of the function is:
//
// ASIN(number)
func (fn *formulaFuncs) ASIN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ASIN requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Asin(arg.Number))
}
// ASINH function calculates the inverse hyperbolic sine of a supplied number.
// The syntax of the function is:
//
// ASINH(number)
func (fn *formulaFuncs) ASINH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ASINH requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Asinh(arg.Number))
}
// ATAN function calculates the arctangent (i.e. the inverse tangent) of a
// given number, and returns an angle, in radians, between -π/2 and +π/2. The
// syntax of the function is:
//
// ATAN(number)
func (fn *formulaFuncs) ATAN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ATAN requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Atan(arg.Number))
}
// ATANH function calculates the inverse hyperbolic tangent of a supplied
// number. The syntax of the function is:
//
// ATANH(number)
func (fn *formulaFuncs) ATANH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ATANH requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type == ArgError {
return arg
}
return newNumberFormulaArg(math.Atanh(arg.Number))
}
// ATAN2 function calculates the arctangent (i.e. the inverse tangent) of a
// given set of x and y coordinates, and returns an angle, in radians, between
// -π/2 and +π/2. The syntax of the function is:
//
// ATAN2(x_num,y_num)
func (fn *formulaFuncs) ATAN2(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ATAN2 requires 2 numeric arguments")
}
x := argsList.Back().Value.(formulaArg).ToNumber()
if x.Type == ArgError {
return x
}
y := argsList.Front().Value.(formulaArg).ToNumber()
if y.Type == ArgError {
return y
}
return newNumberFormulaArg(math.Atan2(x.Number, y.Number))
}
// BASE function converts a number into a supplied base (radix), and returns a
// text representation of the calculated value. The syntax of the function is:
//
// BASE(number,radix,[min_length])
func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "BASE requires at least 2 arguments")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "BASE allows at most 3 arguments")
}
var minLength int
var err error
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
radix := argsList.Front().Next().Value.(formulaArg).ToNumber()
if radix.Type == ArgError {
return radix
}
if int(radix.Number) < 2 || int(radix.Number) > 36 {
return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36")
}
if argsList.Len() > 2 {
if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).Value()); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
}
result := strconv.FormatInt(int64(number.Number), int(radix.Number))
if len(result) < minLength {
result = strings.Repeat("0", minLength-len(result)) + result
}
return newStringFormulaArg(strings.ToUpper(result))
}
// CEILING function rounds a supplied number away from zero, to the nearest
// multiple of a given number. The syntax of the function is:
//
// CEILING(number,significance)
func (fn *formulaFuncs) CEILING(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING allows at most 2 arguments")
}
number, significance, res := 0.0, 1.0, 0.0
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
number = n.Number
if number < 0 {
significance = -1
}
if argsList.Len() > 1 {
s := argsList.Back().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
}
if significance < 0 && number > 0 {
return newErrorFormulaArg(formulaErrorVALUE, "negative sig to CEILING invalid")
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Ceil(number))
}
number, res = math.Modf(number / significance)
if res > 0 {
number++
}
return newNumberFormulaArg(number * significance)
}
// CEILINGdotMATH function rounds a supplied number up to a supplied multiple
// of significance. The syntax of the function is:
//
// CEILING.MATH(number,[significance],[mode])
func (fn *formulaFuncs) CEILINGdotMATH(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH requires at least 1 argument")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH allows at most 3 arguments")
}
number, significance, mode := 0.0, 1.0, 1.0
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
number = n.Number
if number < 0 {
significance = -1
}
if argsList.Len() > 1 {
s := argsList.Front().Next().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Ceil(number))
}
if argsList.Len() > 2 {
m := argsList.Back().Value.(formulaArg).ToNumber()
if m.Type == ArgError {
return m
}
mode = m.Number
}
val, res := math.Modf(number / significance)
if res != 0 {
if number > 0 {
val++
} else if mode < 0 {
val--
}
}
return newNumberFormulaArg(val * significance)
}
// CEILINGdotPRECISE function rounds a supplied number up (regardless of the
// number's sign), to the nearest multiple of a given number. The syntax of
// the function is:
//
// CEILING.PRECISE(number,[significance])
func (fn *formulaFuncs) CEILINGdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE allows at most 2 arguments")
}
number, significance := 0.0, 1.0
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
number = n.Number
if number < 0 {
significance = -1
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Ceil(number))
}
if argsList.Len() > 1 {
s := argsList.Back().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
significance = math.Abs(significance)
if significance == 0 {
return newNumberFormulaArg(significance)
}
}
val, res := math.Modf(number / significance)
if res != 0 {
if number > 0 {
val++
}
}
return newNumberFormulaArg(val * significance)
}
// COMBIN function calculates the number of combinations (in any order) of a
// given number objects from a set. The syntax of the function is:
//
// COMBIN(number,number_chosen)
func (fn *formulaFuncs) COMBIN(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires 2 argument")
}
number, chosen, val := 0.0, 0.0, 1.0
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
number = n.Number
c := argsList.Back().Value.(formulaArg).ToNumber()
if c.Type == ArgError {
return c
}
chosen = c.Number
number, chosen = math.Trunc(number), math.Trunc(chosen)
if chosen > number {
return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires number >= number_chosen")
}
if chosen == number || chosen == 0 {
return newNumberFormulaArg(1)
}
for c := float64(1); c <= chosen; c++ {
val *= (number + 1 - c) / c
}
return newNumberFormulaArg(math.Ceil(val))
}
// COMBINA function calculates the number of combinations, with repetitions,
// of a given number objects from a set. The syntax of the function is:
//
// COMBINA(number,number_chosen)
func (fn *formulaFuncs) COMBINA(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires 2 argument")
}
var number, chosen float64
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
number = n.Number
c := argsList.Back().Value.(formulaArg).ToNumber()
if c.Type == ArgError {
return c
}
chosen = c.Number
number, chosen = math.Trunc(number), math.Trunc(chosen)
if number < chosen {
return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires number > number_chosen")
}
if number == 0 {
return newNumberFormulaArg(number)
}
args := list.New()
args.PushBack(formulaArg{
String: fmt.Sprintf("%g", number+chosen-1),
Type: ArgString,
})
args.PushBack(formulaArg{
String: fmt.Sprintf("%g", number-1),
Type: ArgString,
})
return fn.COMBIN(args)
}
// COS function calculates the cosine of a given angle. The syntax of the
// function is:
//
// COS(number)
func (fn *formulaFuncs) COS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COS requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
return newNumberFormulaArg(math.Cos(val.Number))
}
// COSH function calculates the hyperbolic cosine (cosh) of a supplied number.
// The syntax of the function is:
//
// COSH(number)
func (fn *formulaFuncs) COSH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COSH requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
return newNumberFormulaArg(math.Cosh(val.Number))
}
// COT function calculates the cotangent of a given angle. The syntax of the
// function is:
//
// COT(number)
func (fn *formulaFuncs) COT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COT requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(1 / math.Tan(val.Number))
}
// COTH function calculates the hyperbolic cotangent (coth) of a supplied
// angle. The syntax of the function is:
//
// COTH(number)
func (fn *formulaFuncs) COTH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COTH requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg((math.Exp(val.Number) + math.Exp(-val.Number)) / (math.Exp(val.Number) - math.Exp(-val.Number)))
}
// CSC function calculates the cosecant of a given angle. The syntax of the
// function is:
//
// CSC(number)
func (fn *formulaFuncs) CSC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "CSC requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(1 / math.Sin(val.Number))
}
// CSCH function calculates the hyperbolic cosecant (csch) of a supplied
// angle. The syntax of the function is:
//
// CSCH(number)
func (fn *formulaFuncs) CSCH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "CSCH requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(1 / math.Sinh(val.Number))
}
// DECIMAL function converts a text representation of a number in a specified
// base, into a decimal value. The syntax of the function is:
//
// DECIMAL(text,radix)
func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments")
}
text := argsList.Front().Value.(formulaArg).Value()
var err error
radix := argsList.Back().Value.(formulaArg).ToNumber()
if radix.Type != ArgNumber {
return radix
}
if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) {
text = text[2:]
}
val, err := strconv.ParseInt(text, int(radix.Number), 64)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
return newNumberFormulaArg(float64(val))
}
// DEGREES function converts radians into degrees. The syntax of the function
// is:
//
// DEGREES(angle)
func (fn *formulaFuncs) DEGREES(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DEGREES requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(180.0 / math.Pi * val.Number)
}
// EVEN function rounds a supplied number away from zero (i.e. rounds a
// positive number up and a negative number down), to the next even number.
// The syntax of the function is:
//
// EVEN(number)
func (fn *formulaFuncs) EVEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "EVEN requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
sign := math.Signbit(number.Number)
m, frac := math.Modf(number.Number / 2)
val := m * 2
if frac != 0 {
if !sign {
val += 2
} else {
val -= 2
}
}
return newNumberFormulaArg(val)
}
// EXP function calculates the value of the mathematical constant e, raised to
// the power of a given number. The syntax of the function is:
//
// EXP(number)
func (fn *formulaFuncs) EXP(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "EXP requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Exp(number.Number))
}
// fact returns the factorial of a supplied number.
func fact(number float64) float64 {
val := float64(1)
for i := float64(2); i <= number; i++ {
val *= i
}
return val
}
// FACT function returns the factorial of a supplied number. The syntax of the
// function is:
//
// FACT(number)
func (fn *formulaFuncs) FACT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FACT requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(fact(number.Number))
}
// FACTDOUBLE function returns the double factorial of a supplied number. The
// syntax of the function is:
//
// FACTDOUBLE(number)
func (fn *formulaFuncs) FACTDOUBLE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FACTDOUBLE requires 1 numeric argument")
}
val := 1.0
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
for i := math.Trunc(number.Number); i > 1; i -= 2 {
val *= i
}
return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val)))
}
// FLOOR function rounds a supplied number towards zero to the nearest
// multiple of a specified significance. The syntax of the function is:
//
// FLOOR(number,significance)
func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
significance := argsList.Back().Value.(formulaArg).ToNumber()
if significance.Type == ArgError {
return significance
}
if significance.Number < 0 && number.Number >= 0 {
return newErrorFormulaArg(formulaErrorNUM, "invalid arguments to FLOOR")
}
val := number.Number
val, res := math.Modf(val / significance.Number)
if res != 0 {
if number.Number < 0 && res < 0 {
val--
}
}
return newNumberFormulaArg(val * significance.Number)
}
// FLOORdotMATH function rounds a supplied number down to a supplied multiple
// of significance. The syntax of the function is:
//
// FLOOR.MATH(number,[significance],[mode])
func (fn *formulaFuncs) FLOORdotMATH(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH requires at least 1 argument")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH allows at most 3 arguments")
}
significance, mode := 1.0, 1.0
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number < 0 {
significance = -1
}
if argsList.Len() > 1 {
s := argsList.Front().Next().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Floor(number.Number))
}
if argsList.Len() > 2 {
m := argsList.Back().Value.(formulaArg).ToNumber()
if m.Type == ArgError {
return m
}
mode = m.Number
}
val, res := math.Modf(number.Number / significance)
if res != 0 && number.Number < 0 && mode > 0 {
val--
}
return newNumberFormulaArg(val * significance)
}
// FLOORdotPRECISE function rounds a supplied number down to a supplied
// multiple of significance. The syntax of the function is:
//
// FLOOR.PRECISE(number,[significance])
func (fn *formulaFuncs) FLOORdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE allows at most 2 arguments")
}
var significance float64
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number < 0 {
significance = -1
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Floor(number.Number))
}
if argsList.Len() > 1 {
s := argsList.Back().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
significance = math.Abs(significance)
if significance == 0 {
return newNumberFormulaArg(significance)
}
}
val, res := math.Modf(number.Number / significance)
if res != 0 {
if number.Number < 0 {
val--
}
}
return newNumberFormulaArg(val * significance)
}
// gcd returns the greatest common divisor of two supplied integers.
func gcd(x, y float64) float64 {
x, y = math.Trunc(x), math.Trunc(y)
if x == 0 {
return y
}
if y == 0 {
return x
}
for x != y {
if x > y {
x = x - y
} else {
y = y - x
}
}
return x
}
// GCD function returns the greatest common divisor of two or more supplied
// integers. The syntax of the function is:
//
// GCD(number1,[number2],...)
func (fn *formulaFuncs) GCD(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "GCD requires at least 1 argument")
}
var (
val float64
nums []float64
)
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
num := token.ToNumber()
if num.Type == ArgError {
return num
}
val = num.Number
case ArgNumber:
val = token.Number
}
nums = append(nums, val)
}
if nums[0] < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments")
}
if len(nums) == 1 {
return newNumberFormulaArg(nums[0])
}
cd := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments")
}
cd = gcd(cd, nums[i])
}
return newNumberFormulaArg(cd)
}
// INT function truncates a supplied number down to the closest integer. The
// syntax of the function is:
//
// INT(number)
func (fn *formulaFuncs) INT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "INT requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
val, frac := math.Modf(number.Number)
if frac < 0 {
val--
}
return newNumberFormulaArg(val)
}
// ISOdotCEILING function rounds a supplied number up (regardless of the
// number's sign), to the nearest multiple of a supplied significance. The
// syntax of the function is:
//
// ISO.CEILING(number,[significance])
func (fn *formulaFuncs) ISOdotCEILING(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING allows at most 2 arguments")
}
var significance float64
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number < 0 {
significance = -1
}
if argsList.Len() == 1 {
return newNumberFormulaArg(math.Ceil(number.Number))
}
if argsList.Len() > 1 {
s := argsList.Back().Value.(formulaArg).ToNumber()
if s.Type == ArgError {
return s
}
significance = s.Number
significance = math.Abs(significance)
if significance == 0 {
return newNumberFormulaArg(significance)
}
}
val, res := math.Modf(number.Number / significance)
if res != 0 {
if number.Number > 0 {
val++
}
}
return newNumberFormulaArg(val * significance)
}
// lcm returns the least common multiple of two supplied integers.
func lcm(a, b float64) float64 {
a = math.Trunc(a)
b = math.Trunc(b)
if a == 0 && b == 0 {
return 0
}
return a * b / gcd(a, b)
}
// LCM function returns the least common multiple of two or more supplied
// integers. The syntax of the function is:
//
// LCM(number1,[number2],...)
func (fn *formulaFuncs) LCM(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "LCM requires at least 1 argument")
}
var (
val float64
nums []float64
err error
)
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
if token.String == "" {
continue
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
val = token.Number
}
nums = append(nums, val)
}
if nums[0] < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments")
}
if len(nums) == 1 {
return newNumberFormulaArg(nums[0])
}
cm := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments")
}
cm = lcm(cm, nums[i])
}
return newNumberFormulaArg(cm)
}
// LN function calculates the natural logarithm of a given number. The syntax
// of the function is:
//
// LN(number)
func (fn *formulaFuncs) LN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LN requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Log(number.Number))
}
// LOG function calculates the logarithm of a given number, to a supplied
// base. The syntax of the function is:
//
// LOG(number,[base])
func (fn *formulaFuncs) LOG(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "LOG requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "LOG allows at most 2 arguments")
}
base := 10.0
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if argsList.Len() > 1 {
b := argsList.Back().Value.(formulaArg).ToNumber()
if b.Type == ArgError {
return b
}
base = b.Number
}
if number.Number == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV)
}
if base == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV)
}
if base == 1 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(math.Log(number.Number) / math.Log(base))
}
// LOG10 function calculates the base 10 logarithm of a given number. The
// syntax of the function is:
//
// LOG10(number)
func (fn *formulaFuncs) LOG10(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LOG10 requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Log10(number.Number))
}
// minor function implement a minor of a matrix A is the determinant of some
// smaller square matrix.
func minor(sqMtx [][]float64, idx int) [][]float64 {
var ret [][]float64
for i := range sqMtx {
if i == 0 {
continue
}
var row []float64
for j := range sqMtx {
if j == idx {
continue
}
row = append(row, sqMtx[i][j])
}
ret = append(ret, row)
}
return ret
}
// det determinant of the 2x2 matrix.
func det(sqMtx [][]float64) float64 {
if len(sqMtx) == 2 {
m00 := sqMtx[0][0]
m01 := sqMtx[0][1]
m10 := sqMtx[1][0]
m11 := sqMtx[1][1]
return m00*m11 - m10*m01
}
var res, sgn float64 = 0, 1
for j := range sqMtx {
res += sgn * sqMtx[0][j] * det(minor(sqMtx, j))
sgn *= -1
}
return res
}
// newNumberMatrix converts a formula arguments matrix to a number matrix.
func newNumberMatrix(arg formulaArg, phalanx bool) (numMtx [][]float64, ele formulaArg) {
rows := len(arg.Matrix)
for r, row := range arg.Matrix {
if phalanx && len(row) != rows {
ele = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
numMtx = append(numMtx, make([]float64, len(row)))
for c, cell := range row {
if cell.Type != ArgNumber {
ele = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
numMtx[r][c] = cell.Number
}
}
return
}
// newFormulaArgMatrix converts the number formula arguments matrix to a
// formula arguments matrix.
func newFormulaArgMatrix(numMtx [][]float64) (arg [][]formulaArg) {
for r, row := range numMtx {
arg = append(arg, make([]formulaArg, len(row)))
for c, cell := range row {
arg[r][c] = newNumberFormulaArg(cell)
}
}
return
}
// MDETERM calculates the determinant of a square matrix. The
// syntax of the function is:
//
// MDETERM(array)
func (fn *formulaFuncs) MDETERM(argsList *list.List) (result formulaArg) {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MDETERM requires 1 argument")
}
numMtx, errArg := newNumberMatrix(argsList.Front().Value.(formulaArg), true)
if errArg.Type == ArgError {
return errArg
}
return newNumberFormulaArg(det(numMtx))
}
// cofactorMatrix returns the matrix A of cofactors.
func cofactorMatrix(i, j int, A [][]float64) float64 {
N, sign := len(A), -1.0
if (i+j)%2 == 0 {
sign = 1
}
var B [][]float64
B = append(B, A...)
for m := 0; m < N; m++ {
for n := j + 1; n < N; n++ {
B[m][n-1] = B[m][n]
}
B[m] = B[m][:len(B[m])-1]
}
for k := i + 1; k < N; k++ {
B[k-1] = B[k]
}
B = B[:len(B)-1]
return sign * det(B)
}
// adjugateMatrix returns transpose of the cofactor matrix A with Cramer's
// rule.
func adjugateMatrix(A [][]float64) (adjA [][]float64) {
N := len(A)
var B [][]float64
for i := 0; i < N; i++ {
adjA = append(adjA, make([]float64, N))
for j := 0; j < N; j++ {
for m := 0; m < N; m++ {
for n := 0; n < N; n++ {
for x := len(B); x <= m; x++ {
B = append(B, []float64{})
}
for k := len(B[m]); k <= n; k++ {
B[m] = append(B[m], 0)
}
B[m][n] = A[m][n]
}
}
adjA[i][j] = cofactorMatrix(j, i, B)
}
}
return
}
// MINVERSE function calculates the inverse of a square matrix. The syntax of
// the function is:
//
// MINVERSE(array)
func (fn *formulaFuncs) MINVERSE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MINVERSE requires 1 argument")
}
numMtx, errArg := newNumberMatrix(argsList.Front().Value.(formulaArg), true)
if errArg.Type == ArgError {
return errArg
}
if detM := det(numMtx); detM != 0 {
datM, invertM := 1/detM, adjugateMatrix(numMtx)
for i := 0; i < len(invertM); i++ {
for j := 0; j < len(invertM[i]); j++ {
invertM[i][j] *= datM
}
}
return newMatrixFormulaArg(newFormulaArgMatrix(invertM))
}
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
// MMULT function calculates the matrix product of two arrays
// (representing matrices). The syntax of the function is:
//
// MMULT(array1,array2)
func (fn *formulaFuncs) MMULT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "MMULT requires 2 argument")
}
arr1 := argsList.Front().Value.(formulaArg)
arr2 := argsList.Back().Value.(formulaArg)
if arr1.Type == ArgNumber && arr2.Type == ArgNumber {
return newNumberFormulaArg(arr1.Number * arr2.Number)
}
numMtx1, errArg1 := newNumberMatrix(arr1, false)
if errArg1.Type == ArgError {
return errArg1
}
numMtx2, errArg2 := newNumberMatrix(arr2, false)
if errArg2.Type == ArgError {
return errArg2
}
array2Rows, array2Cols := len(numMtx2), len(numMtx2[0])
if len(numMtx1[0]) != array2Rows {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
var numMtx [][]float64
var row1, row []float64
var sum float64
for i := 0; i < len(numMtx1); i++ {
numMtx = append(numMtx, []float64{})
row = []float64{}
row1 = numMtx1[i]
for j := 0; j < array2Cols; j++ {
sum = 0
for k := 0; k < array2Rows; k++ {
sum += row1[k] * numMtx2[k][j]
}
for l := len(row); l <= j; l++ {
row = append(row, 0)
}
row[j] = sum
numMtx[i] = row
}
}
return newMatrixFormulaArg(newFormulaArgMatrix(numMtx))
}
// MOD function returns the remainder of a division between two supplied
// numbers. The syntax of the function is:
//
// MOD(number,divisor)
func (fn *formulaFuncs) MOD(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "MOD requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
divisor := argsList.Back().Value.(formulaArg).ToNumber()
if divisor.Type == ArgError {
return divisor
}
if divisor.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, "MOD divide by zero")
}
trunc, rem := math.Modf(number.Number / divisor.Number)
if rem < 0 {
trunc--
}
return newNumberFormulaArg(number.Number - divisor.Number*trunc)
}
// MROUND function rounds a supplied number up or down to the nearest multiple
// of a given number. The syntax of the function is:
//
// MROUND(number,multiple)
func (fn *formulaFuncs) MROUND(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "MROUND requires 2 numeric arguments")
}
n := argsList.Front().Value.(formulaArg).ToNumber()
if n.Type == ArgError {
return n
}
multiple := argsList.Back().Value.(formulaArg).ToNumber()
if multiple.Type == ArgError {
return multiple
}
if multiple.Number == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if multiple.Number < 0 && n.Number > 0 ||
multiple.Number > 0 && n.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
number, res := math.Modf(n.Number / multiple.Number)
if math.Trunc(res+0.5) > 0 {
number++
}
return newNumberFormulaArg(number * multiple.Number)
}
// MULTINOMIAL function calculates the ratio of the factorial of a sum of
// supplied values to the product of factorials of those values. The syntax of
// the function is:
//
// MULTINOMIAL(number1,[number2],...)
func (fn *formulaFuncs) MULTINOMIAL(argsList *list.List) formulaArg {
val, num, denom := 0.0, 0.0, 1.0
var err error
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
if token.String == "" {
continue
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
val = token.Number
}
num += val
denom *= fact(val)
}
return newNumberFormulaArg(fact(num) / denom)
}
// MUNIT function returns the unit matrix for a specified dimension. The
// syntax of the function is:
//
// MUNIT(dimension)
func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MUNIT requires 1 numeric argument")
}
dimension := argsList.Back().Value.(formulaArg).ToNumber()
if dimension.Type == ArgError || dimension.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, dimension.Error)
}
matrix := make([][]formulaArg, 0, int(dimension.Number))
for i := 0; i < int(dimension.Number); i++ {
row := make([]formulaArg, int(dimension.Number))
for j := 0; j < int(dimension.Number); j++ {
if i == j {
row[j] = newNumberFormulaArg(1.0)
} else {
row[j] = newNumberFormulaArg(0.0)
}
}
matrix = append(matrix, row)
}
return newMatrixFormulaArg(matrix)
}
// ODD function ounds a supplied number away from zero (i.e. rounds a positive
// number up and a negative number down), to the next odd number. The syntax
// of the function is:
//
// ODD(number)
func (fn *formulaFuncs) ODD(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ODD requires 1 numeric argument")
}
number := argsList.Back().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if number.Number == 0 {
return newNumberFormulaArg(1)
}
sign := math.Signbit(number.Number)
m, frac := math.Modf((number.Number - 1) / 2)
val := m*2 + 1
if frac != 0 {
if !sign {
val += 2
} else {
val -= 2
}
}
return newNumberFormulaArg(val)
}
// PI function returns the value of the mathematical constant π (pi), accurate
// to 15 digits (14 decimal places). The syntax of the function is:
//
// PI()
func (fn *formulaFuncs) PI(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "PI accepts no arguments")
}
return newNumberFormulaArg(math.Pi)
}
// POWER function calculates a given number, raised to a supplied power.
// The syntax of the function is:
//
// POWER(number,power)
func (fn *formulaFuncs) POWER(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "POWER requires 2 numeric arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type == ArgError {
return x
}
y := argsList.Back().Value.(formulaArg).ToNumber()
if y.Type == ArgError {
return y
}
if x.Number == 0 && y.Number == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if x.Number == 0 && y.Number < 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(math.Pow(x.Number, y.Number))
}
// PRODUCT function returns the product (multiplication) of a supplied set of
// numerical values. The syntax of the function is:
//
// PRODUCT(number1,[number2],...)
func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg {
product := 1.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
num := token.ToNumber()
if num.Type != ArgNumber {
return num
}
product = product * num.Number
case ArgNumber:
product = product * token.Number
case ArgMatrix:
for _, row := range token.Matrix {
for _, cell := range row {
if cell.Type == ArgNumber {
product *= cell.Number
}
}
}
}
}
return newNumberFormulaArg(product)
}
// QUOTIENT function returns the integer portion of a division between two
// supplied numbers. The syntax of the function is:
//
// QUOTIENT(numerator,denominator)
func (fn *formulaFuncs) QUOTIENT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "QUOTIENT requires 2 numeric arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type == ArgError {
return x
}
y := argsList.Back().Value.(formulaArg).ToNumber()
if y.Type == ArgError {
return y
}
if y.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(math.Trunc(x.Number / y.Number))
}
// RADIANS function converts radians into degrees. The syntax of the function is:
//
// RADIANS(angle)
func (fn *formulaFuncs) RADIANS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "RADIANS requires 1 numeric argument")
}
angle := argsList.Front().Value.(formulaArg).ToNumber()
if angle.Type == ArgError {
return angle
}
return newNumberFormulaArg(math.Pi / 180.0 * angle.Number)
}
// RAND function generates a random real number between 0 and 1. The syntax of
// the function is:
//
// RAND()
func (fn *formulaFuncs) RAND(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "RAND accepts no arguments")
}
return newNumberFormulaArg(rand.New(rand.NewSource(time.Now().UnixNano())).Float64())
}
// RANDBETWEEN function generates a random integer between two supplied
// integers. The syntax of the function is:
//
// RANDBETWEEN(bottom,top)
func (fn *formulaFuncs) RANDBETWEEN(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "RANDBETWEEN requires 2 numeric arguments")
}
bottom := argsList.Front().Value.(formulaArg).ToNumber()
if bottom.Type == ArgError {
return bottom
}
top := argsList.Back().Value.(formulaArg).ToNumber()
if top.Type == ArgError {
return top
}
if top.Number < bottom.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
num := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number - bottom.Number + 1))
return newNumberFormulaArg(float64(num + int64(bottom.Number)))
}
// romanNumerals defined a numeral system that originated in ancient Rome and
// remained the usual way of writing numbers throughout Europe well into the
// Late Middle Ages.
type romanNumerals struct {
n float64
s string
}
var romanTable = [][]romanNumerals{
{
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{95, "VC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"},
{995, "VM"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{495, "VD"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"},
{999, "IM"},
{995, "VM"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{499, "ID"},
{495, "VD"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
}
// ROMAN function converts an arabic number to Roman. I.e. for a supplied
// integer, the function returns a text string depicting the roman numeral
// form of the number. The syntax of the function is:
//
// ROMAN(number,[form])
func (fn *formulaFuncs) ROMAN(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "ROMAN requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ROMAN allows at most 2 arguments")
}
var form int
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if argsList.Len() > 1 {
f := argsList.Back().Value.(formulaArg).ToNumber()
if f.Type == ArgError {
return f
}
form = int(f.Number)
if form < 0 {
form = 0
} else if form > 4 {
form = 4
}
}
decimalTable := romanTable[0]
switch form {
case 1:
decimalTable = romanTable[1]
case 2:
decimalTable = romanTable[2]
case 3:
decimalTable = romanTable[3]
case 4:
decimalTable = romanTable[4]
}
val := math.Trunc(number.Number)
buf := bytes.Buffer{}
for _, r := range decimalTable {
for val >= r.n {
buf.WriteString(r.s)
val -= r.n
}
}
return newStringFormulaArg(buf.String())
}
type roundMode byte
const (
closest roundMode = iota
down
up
)
// round rounds a supplied number up or down.
func (fn *formulaFuncs) round(number, digits float64, mode roundMode) float64 {
var significance float64
if digits > 0 {
significance = math.Pow(1/10.0, digits)
} else {
significance = math.Pow(10.0, -digits)
}
val, res := math.Modf(number / significance)
switch mode {
case closest:
const eps = 0.499999999
if res >= eps {
val++
} else if res <= -eps {
val--
}
case down:
case up:
if res > 0 {
val++
} else if res < 0 {
val--
}
}
return val * significance
}
// ROUND function rounds a supplied number up or down, to a specified number
// of decimal places. The syntax of the function is:
//
// ROUND(number,num_digits)
func (fn *formulaFuncs) ROUND(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ROUND requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
digits := argsList.Back().Value.(formulaArg).ToNumber()
if digits.Type == ArgError {
return digits
}
return newNumberFormulaArg(fn.round(number.Number, digits.Number, closest))
}
// ROUNDDOWN function rounds a supplied number down towards zero, to a
// specified number of decimal places. The syntax of the function is:
//
// ROUNDDOWN(number,num_digits)
func (fn *formulaFuncs) ROUNDDOWN(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ROUNDDOWN requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
digits := argsList.Back().Value.(formulaArg).ToNumber()
if digits.Type == ArgError {
return digits
}
return newNumberFormulaArg(fn.round(number.Number, digits.Number, down))
}
// ROUNDUP function rounds a supplied number up, away from zero, to a
// specified number of decimal places. The syntax of the function is:
//
// ROUNDUP(number,num_digits)
func (fn *formulaFuncs) ROUNDUP(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ROUNDUP requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
digits := argsList.Back().Value.(formulaArg).ToNumber()
if digits.Type == ArgError {
return digits
}
return newNumberFormulaArg(fn.round(number.Number, digits.Number, up))
}
// SEC function calculates the secant of a given angle. The syntax of the
// function is:
//
// SEC(number)
func (fn *formulaFuncs) SEC(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SEC requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Cos(number.Number))
}
// SECH function calculates the hyperbolic secant (sech) of a supplied angle.
// The syntax of the function is:
//
// SECH(number)
func (fn *formulaFuncs) SECH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SECH requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(1 / math.Cosh(number.Number))
}
// SERIESSUM function returns the sum of a power series. The syntax of the
// function is:
//
// SERIESSUM(x,n,m,coefficients)
func (fn *formulaFuncs) SERIESSUM(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "SERIESSUM requires 4 arguments")
}
var x, n, m formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if n = argsList.Front().Next().Value.(formulaArg).ToNumber(); n.Type != ArgNumber {
return n
}
if m = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); m.Type != ArgNumber {
return m
}
var result, i float64
for _, coefficient := range argsList.Back().Value.(formulaArg).ToList() {
if coefficient.Value() == "" {
continue
}
num := coefficient.ToNumber()
if num.Type != ArgNumber {
return num
}
result += num.Number * math.Pow(x.Number, n.Number+(m.Number*i))
i++
}
return newNumberFormulaArg(result)
}
// SIGN function returns the arithmetic sign (+1, -1 or 0) of a supplied
// number. I.e. if the number is positive, the Sign function returns +1, if
// the number is negative, the function returns -1 and if the number is 0
// (zero), the function returns 0. The syntax of the function is:
//
// SIGN(number)
func (fn *formulaFuncs) SIGN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SIGN requires 1 numeric argument")
}
val := argsList.Front().Value.(formulaArg).ToNumber()
if val.Type == ArgError {
return val
}
if val.Number < 0 {
return newNumberFormulaArg(-1)
}
if val.Number > 0 {
return newNumberFormulaArg(1)
}
return newNumberFormulaArg(0)
}
// SIN function calculates the sine of a given angle. The syntax of the
// function is:
//
// SIN(number)
func (fn *formulaFuncs) SIN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SIN requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Sin(number.Number))
}
// SINH function calculates the hyperbolic sine (sinh) of a supplied number.
// The syntax of the function is:
//
// SINH(number)
func (fn *formulaFuncs) SINH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SINH requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Sinh(number.Number))
}
// SQRT function calculates the positive square root of a supplied number. The
// syntax of the function is:
//
// SQRT(number)
func (fn *formulaFuncs) SQRT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SQRT requires 1 numeric argument")
}
value := argsList.Front().Value.(formulaArg).ToNumber()
if value.Type == ArgError {
return value
}
if value.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(math.Sqrt(value.Number))
}
// SQRTPI function returns the square root of a supplied number multiplied by
// the mathematical constant, π. The syntax of the function is:
//
// SQRTPI(number)
func (fn *formulaFuncs) SQRTPI(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SQRTPI requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Sqrt(number.Number * math.Pi))
}
// STDEV function calculates the sample standard deviation of a supplied set
// of values. The syntax of the function is:
//
// STDEV(number1,[number2],...)
func (fn *formulaFuncs) STDEV(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "STDEV requires at least 1 argument")
}
return fn.stdev(false, argsList)
}
// STDEVdotS function calculates the sample standard deviation of a supplied
// set of values. The syntax of the function is:
//
// STDEV.S(number1,[number2],...)
func (fn *formulaFuncs) STDEVdotS(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "STDEV.S requires at least 1 argument")
}
return fn.stdev(false, argsList)
}
// STDEVA function estimates standard deviation based on a sample. The
// standard deviation is a measure of how widely values are dispersed from
// the average value (the mean). The syntax of the function is:
//
// STDEVA(number1,[number2],...)
func (fn *formulaFuncs) STDEVA(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "STDEVA requires at least 1 argument")
}
return fn.stdev(true, argsList)
}
// calcStdevPow is part of the implementation stdev.
func calcStdevPow(result, count float64, n, m formulaArg) (float64, float64) {
if result == -1 {
result = math.Pow(n.Number-m.Number, 2)
} else {
result += math.Pow(n.Number-m.Number, 2)
}
count++
return result, count
}
// calcStdev is part of the implementation stdev.
func calcStdev(stdeva bool, result, count float64, mean, token formulaArg) (float64, float64) {
for _, row := range token.ToList() {
if row.Type == ArgNumber || row.Type == ArgString {
if !stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") {
continue
} else if stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") {
num := row.ToBool()
if num.Type == ArgNumber {
result, count = calcStdevPow(result, count, num, mean)
continue
}
} else {
num := row.ToNumber()
if num.Type == ArgNumber {
result, count = calcStdevPow(result, count, num, mean)
}
}
}
}
return result, count
}
// stdev is an implementation of the formula functions STDEV and STDEVA.
func (fn *formulaFuncs) stdev(stdeva bool, argsList *list.List) formulaArg {
count, result := -1.0, -1.0
var mean formulaArg
if stdeva {
mean = fn.AVERAGEA(argsList)
} else {
mean = fn.AVERAGE(argsList)
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString, ArgNumber:
if !stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") {
continue
} else if stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") {
num := token.ToBool()
if num.Type == ArgNumber {
result, count = calcStdevPow(result, count, num, mean)
continue
}
} else {
num := token.ToNumber()
if num.Type == ArgNumber {
result, count = calcStdevPow(result, count, num, mean)
}
}
case ArgList, ArgMatrix:
result, count = calcStdev(stdeva, result, count, mean, token)
}
}
if count > 0 && result >= 0 {
return newNumberFormulaArg(math.Sqrt(result / count))
}
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
// POISSONdotDIST function calculates the Poisson Probability Mass Function or
// the Cumulative Poisson Probability Function for a supplied set of
// parameters. The syntax of the function is:
//
// POISSON.DIST(x,mean,cumulative)
func (fn *formulaFuncs) POISSONdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "POISSON.DIST requires 3 arguments")
}
return fn.POISSON(argsList)
}
// POISSON function calculates the Poisson Probability Mass Function or the
// Cumulative Poisson Probability Function for a supplied set of parameters.
// The syntax of the function is:
//
// POISSON(x,mean,cumulative)
func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "POISSON requires 3 arguments")
}
var x, mean, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if x.Number < 0 || mean.Number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if cumulative.Number == 1 {
summer := 0.0
floor := math.Floor(x.Number)
for i := 0; i <= int(floor); i++ {
summer += math.Pow(mean.Number, float64(i)) / fact(float64(i))
}
return newNumberFormulaArg(math.Exp(0-mean.Number) * summer)
}
return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number))
}
// prepareProbArgs checking and prepare arguments for the formula function
// PROB.
func prepareProbArgs(argsList *list.List) []formulaArg {
if argsList.Len() < 3 {
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at least 3 arguments")}
}
if argsList.Len() > 4 {
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at most 4 arguments")}
}
var lower, upper formulaArg
xRange := argsList.Front().Value.(formulaArg)
probRange := argsList.Front().Next().Value.(formulaArg)
if lower = argsList.Front().Next().Next().Value.(formulaArg); lower.Type != ArgNumber {
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)}
}
upper = lower
if argsList.Len() == 4 {
if upper = argsList.Back().Value.(formulaArg); upper.Type != ArgNumber {
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)}
}
}
nR1, nR2 := len(xRange.Matrix), len(probRange.Matrix)
if nR1 == 0 || nR2 == 0 {
return []formulaArg{newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)}
}
if nR1 != nR2 {
return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)}
}
nC1, nC2 := len(xRange.Matrix[0]), len(probRange.Matrix[0])
if nC1 != nC2 {
return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)}
}
return []formulaArg{xRange, probRange, lower, upper}
}
// PROB function calculates the probability associated with a given range. The
// syntax of the function is:
//
// PROB(x_range,prob_range,lower_limit,[upper_limit])
func (fn *formulaFuncs) PROB(argsList *list.List) formulaArg {
args := prepareProbArgs(argsList)
if len(args) == 1 {
return args[0]
}
xRange, probRange, lower, upper := args[0], args[1], args[2], args[3]
var sum, res, fP, fW float64
var stop bool
for r := 0; r < len(xRange.Matrix) && !stop; r++ {
for c := 0; c < len(xRange.Matrix[0]) && !stop; c++ {
p := probRange.Matrix[r][c]
x := xRange.Matrix[r][c]
if p.Type == ArgNumber && x.Type == ArgNumber {
if fP, fW = p.Number, x.Number; fP < 0 || fP > 1 {
stop = true
continue
}
if sum += fP; fW >= lower.Number && fW <= upper.Number {
res += fP
}
continue
}
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
if stop || math.Abs(sum-1) > 1.0e-7 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(res)
}
// SUBTOTAL function performs a specified calculation (e.g. the sum, product,
// average, etc.) for a supplied set of values. The syntax of the function is:
//
// SUBTOTAL(function_num,ref1,[ref2],...)
func (fn *formulaFuncs) SUBTOTAL(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL requires at least 2 arguments")
}
var fnNum formulaArg
if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber {
return fnNum
}
subFn, ok := map[int]func(argsList *list.List) formulaArg{
1: fn.AVERAGE, 101: fn.AVERAGE,
2: fn.COUNT, 102: fn.COUNT,
3: fn.COUNTA, 103: fn.COUNTA,
4: fn.MAX, 104: fn.MAX,
5: fn.MIN, 105: fn.MIN,
6: fn.PRODUCT, 106: fn.PRODUCT,
7: fn.STDEV, 107: fn.STDEV,
8: fn.STDEVP, 108: fn.STDEVP,
9: fn.SUM, 109: fn.SUM,
10: fn.VAR, 110: fn.VAR,
11: fn.VARP, 111: fn.VARP,
}[int(fnNum.Number)]
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL has invalid function_num")
}
subArgList := list.New().Init()
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
subArgList.PushBack(arg.Value.(formulaArg))
}
return subFn(subArgList)
}
// SUM function adds together a supplied set of numbers and returns the sum of
// these values. The syntax of the function is:
//
// SUM(number1,[number2],...)
func (fn *formulaFuncs) SUM(argsList *list.List) formulaArg {
var sum float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgError:
return token
case ArgString:
if num := token.ToNumber(); num.Type == ArgNumber {
sum += num.Number
}
case ArgNumber:
sum += token.Number
case ArgMatrix:
for _, row := range token.Matrix {
for _, value := range row {
if num := value.ToNumber(); num.Type == ArgNumber {
sum += num.Number
}
}
}
}
}
return newNumberFormulaArg(sum)
}
// SUMIF function finds the values in a supplied array, that satisfy a given
// criteria, and returns the sum of the corresponding values in a second
// supplied array. The syntax of the function is:
//
// SUMIF(range,criteria,[sum_range])
func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 arguments")
}
criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
rangeMtx := argsList.Front().Value.(formulaArg).Matrix
var sumRange [][]formulaArg
if argsList.Len() == 3 {
sumRange = argsList.Back().Value.(formulaArg).Matrix
}
var sum float64
var arg formulaArg
for rowIdx, row := range rangeMtx {
for colIdx, cell := range row {
arg = cell
if arg.Type == ArgEmpty {
continue
}
if ok, _ := formulaCriteriaEval(arg, criteria); ok {
if argsList.Len() == 3 {
if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx {
arg = sumRange[rowIdx][colIdx]
}
}
if arg.Type == ArgNumber {
sum += arg.Number
}
}
}
}
return newNumberFormulaArg(sum)
}
// SUMIFS function finds values in one or more supplied arrays, that satisfy a
// set of criteria, and returns the sum of the corresponding values in a
// further supplied array. The syntax of the function is:
//
// SUMIFS(sum_range,criteria_range1,criteria1,[criteria_range2,criteria2],...)
func (fn *formulaFuncs) SUMIFS(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "SUMIFS requires at least 3 arguments")
}
if argsList.Len()%2 != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var args []formulaArg
sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
for _, ref := range formulaIfsMatch(args) {
if ref.Row >= len(sumRange) || ref.Col >= len(sumRange[ref.Row]) {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber {
sum += num.Number
}
}
return newNumberFormulaArg(sum)
}
// sumproduct is an implementation of the formula function SUMPRODUCT.
func (fn *formulaFuncs) sumproduct(argsList *list.List) formulaArg {
var (
argType ArgType
n int
res []float64
sum float64
)
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
if argType == ArgUnknown {
argType = token.Type
}
if token.Type != argType {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
switch token.Type {
case ArgString, ArgNumber:
if num := token.ToNumber(); num.Type == ArgNumber {
sum = fn.PRODUCT(argsList).Number
continue
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
case ArgMatrix:
args := token.ToList()
if res == nil {
n = len(args)
res = make([]float64, n)
for i := range res {
res[i] = 1.0
}
}
if len(args) != n {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for i, value := range args {
num := value.ToNumber()
if num.Type != ArgNumber && value.Value() != "" {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
res[i] = res[i] * num.Number
}
}
}
for _, r := range res {
sum += r
}
return newNumberFormulaArg(sum)
}
// SUMPRODUCT function returns the sum of the products of the corresponding
// values in a set of supplied arrays. The syntax of the function is:
//
// SUMPRODUCT(array1,[array2],[array3],...)
func (fn *formulaFuncs) SUMPRODUCT(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SUMPRODUCT requires at least 1 argument")
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
if token := arg.Value.(formulaArg); token.Type == ArgError {
return token
}
}
return fn.sumproduct(argsList)
}
// SUMSQ function returns the sum of squares of a supplied set of values. The
// syntax of the function is:
//
// SUMSQ(number1,[number2],...)
func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg {
var val, sq float64
var err error
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
if token.String == "" {
continue
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
sq += val * val
case ArgNumber:
sq += token.Number * token.Number
case ArgMatrix:
for _, row := range token.Matrix {
for _, value := range row {
if value.Value() == "" {
continue
}
if val, err = strconv.ParseFloat(value.Value(), 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
sq += val * val
}
}
}
}
return newNumberFormulaArg(sq)
}
// sumx is an implementation of the formula functions SUMX2MY2, SUMX2PY2 and
// SUMXMY2.
func (fn *formulaFuncs) sumx(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
}
array1 := argsList.Front().Value.(formulaArg)
array2 := argsList.Back().Value.(formulaArg)
left, right := array1.ToList(), array2.ToList()
n := len(left)
if n != len(right) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
result := 0.0
for i := 0; i < n; i++ {
if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 {
switch name {
case "SUMX2MY2":
result += lhs.Number*lhs.Number - rhs.Number*rhs.Number
case "SUMX2PY2":
result += lhs.Number*lhs.Number + rhs.Number*rhs.Number
default:
result += (lhs.Number - rhs.Number) * (lhs.Number - rhs.Number)
}
}
}
return newNumberFormulaArg(result)
}
// SUMX2MY2 function returns the sum of the differences of squares of two
// supplied sets of values. The syntax of the function is:
//
// SUMX2MY2(array_x,array_y)
func (fn *formulaFuncs) SUMX2MY2(argsList *list.List) formulaArg {
return fn.sumx("SUMX2MY2", argsList)
}
// SUMX2PY2 function returns the sum of the sum of squares of two supplied sets
// of values. The syntax of the function is:
//
// SUMX2PY2(array_x,array_y)
func (fn *formulaFuncs) SUMX2PY2(argsList *list.List) formulaArg {
return fn.sumx("SUMX2PY2", argsList)
}
// SUMXMY2 function returns the sum of the squares of differences between
// corresponding values in two supplied arrays. The syntax of the function
// is:
//
// SUMXMY2(array_x,array_y)
func (fn *formulaFuncs) SUMXMY2(argsList *list.List) formulaArg {
return fn.sumx("SUMXMY2", argsList)
}
// TAN function calculates the tangent of a given angle. The syntax of the
// function is:
//
// TAN(number)
func (fn *formulaFuncs) TAN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TAN requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Tan(number.Number))
}
// TANH function calculates the hyperbolic tangent (tanh) of a supplied
// number. The syntax of the function is:
//
// TANH(number)
func (fn *formulaFuncs) TANH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TANH requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
return newNumberFormulaArg(math.Tanh(number.Number))
}
// TRUNC function truncates a supplied number to a specified number of decimal
// places. The syntax of the function is:
//
// TRUNC(number,[number_digits])
func (fn *formulaFuncs) TRUNC(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "TRUNC requires at least 1 argument")
}
var digits, adjust, rtrim float64
var err error
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type == ArgError {
return number
}
if argsList.Len() > 1 {
d := argsList.Back().Value.(formulaArg).ToNumber()
if d.Type == ArgError {
return d
}
digits = d.Number
digits = math.Floor(digits)
}
adjust = math.Pow(10, digits)
x := int((math.Abs(number.Number) - math.Abs(float64(int(number.Number)))) * adjust)
if x != 0 {
if rtrim, err = strconv.ParseFloat(strings.TrimRight(strconv.Itoa(x), "0"), 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
}
if (digits > 0) && (rtrim < adjust/10) {
return newNumberFormulaArg(number.Number)
}
return newNumberFormulaArg(float64(int(number.Number*adjust)) / adjust)
}
// Statistical Functions
// AVEDEV function calculates the average deviation of a supplied set of
// values. The syntax of the function is:
//
// AVEDEV(number1,[number2],...)
func (fn *formulaFuncs) AVEDEV(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "AVEDEV requires at least 1 argument")
}
average := fn.AVERAGE(argsList)
if average.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
result, count := 0.0, 0.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
num := arg.Value.(formulaArg).ToNumber()
if num.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
result += math.Abs(num.Number - average.Number)
count++
}
return newNumberFormulaArg(result / count)
}
// AVERAGE function returns the arithmetic mean of a list of supplied numbers.
// The syntax of the function is:
//
// AVERAGE(number1,[number2],...)
func (fn *formulaFuncs) AVERAGE(argsList *list.List) formulaArg {
var args []formulaArg
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
count, sum := fn.countSum(false, args)
if count == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(sum / count)
}
// AVERAGEA function returns the arithmetic mean of a list of supplied numbers
// with text cell and zero values. The syntax of the function is:
//
// AVERAGEA(number1,[number2],...)
func (fn *formulaFuncs) AVERAGEA(argsList *list.List) formulaArg {
var args []formulaArg
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
count, sum := fn.countSum(true, args)
if count == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(sum / count)
}
// AVERAGEIF function finds the values in a supplied array that satisfy a
// specified criteria, and returns the average (i.e. the statistical mean) of
// the corresponding values in a second supplied array. The syntax of the
// function is:
//
// AVERAGEIF(range,criteria,[average_range])
func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments")
}
var (
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
rangeMtx = argsList.Front().Value.(formulaArg).Matrix
cellRange [][]formulaArg
args []formulaArg
val float64
err error
ok bool
)
if argsList.Len() == 3 {
cellRange = argsList.Back().Value.(formulaArg).Matrix
}
for rowIdx, row := range rangeMtx {
for colIdx, col := range row {
fromVal := col.Value()
if fromVal == "" {
continue
}
if col.Type == ArgString && criteria.Condition.Type != ArgString {
continue
}
ok, _ = formulaCriteriaEval(col, criteria)
if ok {
if argsList.Len() == 3 {
if len(cellRange) > rowIdx && len(cellRange[rowIdx]) > colIdx {
fromVal = cellRange[rowIdx][colIdx].Value()
}
}
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
continue
}
args = append(args, newNumberFormulaArg(val))
}
}
}
count, sum := fn.countSum(false, args)
if count == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(sum / count)
}
// AVERAGEIFS function finds entries in one or more arrays, that satisfy a set
// of supplied criteria, and returns the average (i.e. the statistical mean)
// of the corresponding values in a further supplied array. The syntax of the
// function is:
//
// AVERAGEIFS(average_range,criteria_range1,criteria1,[criteria_range2,criteria2],...)
func (fn *formulaFuncs) AVERAGEIFS(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIFS requires at least 3 arguments")
}
if argsList.Len()%2 != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var args []formulaArg
sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
count := 0.0
for _, ref := range formulaIfsMatch(args) {
if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber {
sum += num.Number
count++
}
}
if count == 0 {
return newErrorFormulaArg(formulaErrorDIV, "AVERAGEIF divide by zero")
}
return newNumberFormulaArg(sum / count)
}
// getBetaHelperContFrac continued fractions for the beta function.
func getBetaHelperContFrac(fX, fA, fB float64) float64 {
var a1, b1, a2, b2, fnorm, cfnew, cf, rm float64
a1, b1, b2 = 1, 1, 1-(fA+fB)/(fA+1)*fX
if b2 == 0 {
a2, fnorm, cf = 0, 1, 1
} else {
a2, fnorm = 1, 1/b2
cf = a2 * fnorm
}
cfnew, rm = 1, 1
fMaxIter, fMachEps := 50000.0, 2.22045e-016
bfinished := false
for rm < fMaxIter && !bfinished {
apl2m := fA + 2*rm
d2m := rm * (fB - rm) * fX / ((apl2m - 1) * apl2m)
d2m1 := -(fA + rm) * (fA + fB + rm) * fX / (apl2m * (apl2m + 1))
a1 = math.FMA(d2m, a1, a2) * fnorm
b1 = math.FMA(d2m, b1, b2) * fnorm
a2 = math.FMA(d2m1*a2, fnorm, a1)
b2 = math.FMA(d2m1*b2, fnorm, b1)
if b2 != 0 {
fnorm = 1 / b2
cfnew = a2 * fnorm
bfinished = math.Abs(cf-cfnew) < math.Abs(cf)*fMachEps
}
cf = cfnew
rm++
}
return cf
}
// getLanczosSum uses a variant of the Lanczos sum with a rational function.
func getLanczosSum(fZ float64) float64 {
num := []float64{
23531376880.41075968857200767445163675473,
42919803642.64909876895789904700198885093,
35711959237.35566804944018545154716670596,
17921034426.03720969991975575445893111267,
6039542586.35202800506429164430729792107,
1439720407.311721673663223072794912393972,
248874557.8620541565114603864132294232163,
31426415.58540019438061423162831820536287,
2876370.628935372441225409051620849613599,
186056.2653952234950402949897160456992822,
8071.672002365816210638002902272250613822,
210.8242777515793458725097339207133627117,
2.506628274631000270164908177133837338626,
}
denom := []float64{
0,
39916800,
120543840,
150917976,
105258076,
45995730,
13339535,
2637558,
357423,
32670,
1925,
66,
1,
}
var sumNum, sumDenom, zInv float64
if fZ <= 1 {
sumNum = num[12]
sumDenom = denom[12]
for i := 11; i >= 0; i-- {
sumNum = math.FMA(sumNum, fZ, num[i])
sumDenom = math.FMA(sumDenom, fZ, denom[i])
}
} else {
zInv = 1 / fZ
sumNum = num[0]
sumDenom = denom[0]
for i := 1; i <= 12; i++ {
sumNum = math.FMA(sumNum, zInv, num[i])
sumDenom = math.FMA(sumDenom, zInv, denom[i])
}
}
return sumNum / sumDenom
}
// getBeta return beta distribution.
func getBeta(fAlpha, fBeta float64) float64 {
var fA, fB float64
if fAlpha > fBeta {
fA = fAlpha
fB = fBeta
} else {
fA = fBeta
fB = fAlpha
}
const maxGammaArgument = 171.624376956302
if fA+fB < maxGammaArgument {
return math.Gamma(fA) / math.Gamma(fA+fB) * math.Gamma(fB)
}
fg := 6.024680040776729583740234375
fgm := fg - 0.5
fLanczos := getLanczosSum(fA)
fLanczos /= getLanczosSum(fA + fB)
fLanczos *= getLanczosSum(fB)
fABgm := fA + fB + fgm
fLanczos *= math.Sqrt((fABgm / (fA + fgm)) / (fB + fgm))
fTempA := fB / (fA + fgm)
fTempB := fA / (fB + fgm)
fResult := math.Exp(-fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm)
fResult *= fLanczos
return fResult
}
// getBetaDistPDF is an implementation for the Beta probability density
// function.
func getBetaDistPDF(fX, fA, fB float64) float64 {
if fX <= 0 || fX >= 1 {
return 0
}
fLogDblMax, fLogDblMin := math.Log(1.79769e+308), math.Log(2.22507e-308)
fLogY := math.Log(0.5 - fX + 0.5)
if fX < 0.1 {
fLogY = math.Log1p(-fX)
}
fLogX := math.Log(fX)
fAm1LogX := (fA - 1) * fLogX
fBm1LogY := (fB - 1) * fLogY
fLogBeta := getLogBeta(fA, fB)
if fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin && fBm1LogY < fLogDblMax &&
fBm1LogY > fLogDblMin && fLogBeta < fLogDblMax && fLogBeta > fLogDblMin &&
fAm1LogX+fBm1LogY < fLogDblMax && fAm1LogX+fBm1LogY > fLogDblMin {
return math.Pow(fX, fA-1) * math.Pow(0.5-fX+0.5, fB-1) / getBeta(fA, fB)
}
return math.Exp(fAm1LogX + fBm1LogY - fLogBeta)
}
// getLogBeta return beta with logarithm.
func getLogBeta(fAlpha, fBeta float64) float64 {
var fA, fB float64
if fAlpha > fBeta {
fA, fB = fAlpha, fBeta
} else {
fA, fB = fBeta, fAlpha
}
fg := 6.024680040776729583740234375
fgm := fg - 0.5
fLanczos := getLanczosSum(fA)
fLanczos /= getLanczosSum(fA + fB)
fLanczos *= getLanczosSum(fB)
fLogLanczos := math.Log(fLanczos)
fABgm := fA + fB + fgm
fLogLanczos += 0.5 * (math.Log(fABgm) - math.Log(fA+fgm) - math.Log(fB+fgm))
fTempA := fB / (fA + fgm)
fTempB := fA / (fB + fgm)
fResult := -fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm
fResult += fLogLanczos
return fResult
}
// getBetaDist is an implementation for the beta distribution function.
func getBetaDist(fXin, fAlpha, fBeta float64) float64 {
if fXin <= 0 {
return 0
}
if fXin >= 1 {
return 1
}
if fBeta == 1 {
return math.Pow(fXin, fAlpha)
}
if fAlpha == 1 {
return -math.Expm1(fBeta * math.Log1p(-fXin))
}
var fResult float64
fY, flnY := (0.5-fXin)+0.5, math.Log1p(-fXin)
fX, flnX := fXin, math.Log(fXin)
fA, fB := fAlpha, fBeta
bReflect := fXin > fAlpha/(fAlpha+fBeta)
if bReflect {
fA = fBeta
fB = fAlpha
fX = fY
fY = fXin
flnX = flnY
flnY = math.Log(fXin)
}
fResult = getBetaHelperContFrac(fX, fA, fB) / fA
fP, fQ := fA/(fA+fB), fB/(fA+fB)
var fTemp float64
if fA > 1 && fB > 1 && fP < 0.97 && fQ < 0.97 {
fTemp = getBetaDistPDF(fX, fA, fB) * fX * fY
} else {
fTemp = math.Exp(fA*flnX + fB*flnY - getLogBeta(fA, fB))
}
fResult *= fTemp
if bReflect {
fResult = 0.5 - fResult + 0.5
}
return fResult
}
// prepareBETAdotDISTArgs checking and prepare arguments for the formula
// function BETA.DIST.
func (fn *formulaFuncs) prepareBETAdotDISTArgs(argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at least 4 arguments")
}
if argsList.Len() > 6 {
return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at most 6 arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
alpha := argsList.Front().Next().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
cumulative := argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool()
if cumulative.Type != ArgNumber {
return cumulative
}
a, b := newNumberFormulaArg(0), newNumberFormulaArg(1)
if argsList.Len() > 4 {
if a = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber {
return a
}
}
if argsList.Len() == 6 {
if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber {
return b
}
}
return newListFormulaArg([]formulaArg{x, alpha, beta, cumulative, a, b})
}
// BETAdotDIST function calculates the cumulative beta distribution function
// or the probability density function of the Beta distribution, for a
// supplied set of parameters. The syntax of the function is:
//
// BETA.DIST(x,alpha,beta,cumulative,[A],[B])
func (fn *formulaFuncs) BETAdotDIST(argsList *list.List) formulaArg {
args := fn.prepareBETAdotDISTArgs(argsList)
if args.Type != ArgList {
return args
}
x, alpha, beta, cumulative, a, b := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]
if x.Number < a.Number || x.Number > b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if a.Number == b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
scale := b.Number - a.Number
x.Number = (x.Number - a.Number) / scale
if cumulative.Number == 1 {
return newNumberFormulaArg(getBetaDist(x.Number, alpha.Number, beta.Number))
}
return newNumberFormulaArg(getBetaDistPDF(x.Number, alpha.Number, beta.Number) / scale)
}
// BETADIST function calculates the cumulative beta probability density
// function for a supplied set of parameters. The syntax of the function is:
//
// BETADIST(x,alpha,beta,[A],[B])
func (fn *formulaFuncs) BETADIST(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at most 5 arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
alpha := argsList.Front().Next().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
a, b := newNumberFormulaArg(0), newNumberFormulaArg(1)
if argsList.Len() > 3 {
if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber {
return a
}
}
if argsList.Len() == 5 {
if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber {
return b
}
}
if x.Number < a.Number || x.Number > b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if a.Number == b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(getBetaDist((x.Number-a.Number)/(b.Number-a.Number), alpha.Number, beta.Number))
}
// d1mach returns double precision real machine constants.
func d1mach(i int) float64 {
arr := []float64{
2.2250738585072014e-308,
1.7976931348623158e+308,
1.1102230246251565e-16,
2.2204460492503131e-16,
0.301029995663981195,
}
if i > len(arr) {
return 0
}
return arr[i-1]
}
// chebyshevInit determines the number of terms for the double precision
// orthogonal series "dos" needed to insure the error is no larger
// than "eta". Ordinarily eta will be chosen to be one-tenth machine
// precision.
func chebyshevInit(nos int, eta float64, dos []float64) int {
i, e := 0, 0.0
if nos < 1 {
return 0
}
for ii := 1; ii <= nos; ii++ {
i = nos - ii
e += math.Abs(dos[i])
if e > eta {
return i
}
}
return i
}
// chebyshevEval evaluates the n-term Chebyshev series "a" at "x".
func chebyshevEval(n int, x float64, a []float64) float64 {
if n < 1 || n > 1000 || x < -1.1 || x > 1.1 {
return math.NaN()
}
twox, b0, b1, b2 := x*2, 0.0, 0.0, 0.0
for i := 1; i <= n; i++ {
b2 = b1
b1 = b0
b0 = twox*b1 - b2 + a[n-i]
}
return (b0 - b2) * 0.5
}
// lgammacor is an implementation for the log(gamma) correction.
func lgammacor(x float64) float64 {
algmcs := []float64{
0.1666389480451863247205729650822, -0.1384948176067563840732986059135e-4,
0.9810825646924729426157171547487e-8, -0.1809129475572494194263306266719e-10,
0.6221098041892605227126015543416e-13, -0.3399615005417721944303330599666e-15,
0.2683181998482698748957538846666e-17, -0.2868042435334643284144622399999e-19,
0.3962837061046434803679306666666e-21, -0.6831888753985766870111999999999e-23,
0.1429227355942498147573333333333e-24, -0.3547598158101070547199999999999e-26,
0.1025680058010470912000000000000e-27, -0.3401102254316748799999999999999e-29,
0.1276642195630062933333333333333e-30,
}
nalgm := chebyshevInit(15, d1mach(3), algmcs)
xbig := 1.0 / math.Sqrt(d1mach(3))
xmax := math.Exp(math.Min(math.Log(d1mach(2)/12.0), -math.Log(12.0*d1mach(1))))
if x < 10.0 {
return math.NaN()
} else if x >= xmax {
return 4.930380657631324e-32
} else if x < xbig {
tmp := 10.0 / x
return chebyshevEval(nalgm, tmp*tmp*2.0-1.0, algmcs) / x
}
return 1.0 / (x * 12.0)
}
// logrelerr compute the relative error logarithm.
func logrelerr(x float64) float64 {
alnrcs := []float64{
0.10378693562743769800686267719098e+1, -0.13364301504908918098766041553133,
0.19408249135520563357926199374750e-1, -0.30107551127535777690376537776592e-2,
0.48694614797154850090456366509137e-3, -0.81054881893175356066809943008622e-4,
0.13778847799559524782938251496059e-4, -0.23802210894358970251369992914935e-5,
0.41640416213865183476391859901989e-6, -0.73595828378075994984266837031998e-7,
0.13117611876241674949152294345011e-7, -0.23546709317742425136696092330175e-8,
0.42522773276034997775638052962567e-9, -0.77190894134840796826108107493300e-10,
0.14075746481359069909215356472191e-10, -0.25769072058024680627537078627584e-11,
0.47342406666294421849154395005938e-12, -0.87249012674742641745301263292675e-13,
0.16124614902740551465739833119115e-13, -0.29875652015665773006710792416815e-14,
0.55480701209082887983041321697279e-15, -0.10324619158271569595141333961932e-15,
0.19250239203049851177878503244868e-16, -0.35955073465265150011189707844266e-17,
0.67264542537876857892194574226773e-18, -0.12602624168735219252082425637546e-18,
0.23644884408606210044916158955519e-19, -0.44419377050807936898878389179733e-20,
0.83546594464034259016241293994666e-21, -0.15731559416479562574899253521066e-21,
0.29653128740247422686154369706666e-22, -0.55949583481815947292156013226666e-23,
0.10566354268835681048187284138666e-23, -0.19972483680670204548314999466666e-24,
0.37782977818839361421049855999999e-25, -0.71531586889081740345038165333333e-26,
0.13552488463674213646502024533333e-26, -0.25694673048487567430079829333333e-27,
0.48747756066216949076459519999999e-28, -0.92542112530849715321132373333333e-29,
0.17578597841760239233269760000000e-29, -0.33410026677731010351377066666666e-30,
0.63533936180236187354180266666666e-31,
}
nlnrel := chebyshevInit(43, 0.1*d1mach(3), alnrcs)
if x <= -1 {
return math.NaN()
}
if math.Abs(x) <= 0.375 {
return x * (1.0 - x*chebyshevEval(nlnrel, x/0.375, alnrcs))
}
return math.Log(x + 1.0)
}
// logBeta is an implementation for the log of the beta distribution
// function.
func logBeta(a, b float64) float64 {
corr, p, q := 0.0, a, a
if b < p {
p = b
}
if b > q {
q = b
}
if p < 0 {
return math.NaN()
}
if p == 0 {
return math.MaxFloat64
}
if p >= 10.0 {
corr = lgammacor(p) + lgammacor(q) - lgammacor(p+q)
f1 := q * logrelerr(-p/(p+q))
return math.Log(q)*-0.5 + 0.918938533204672741780329736406 + corr + (p-0.5)*math.Log(p/(p+q)) + math.Nextafter(f1, f1)
}
if q >= 10 {
corr = lgammacor(q) - lgammacor(p+q)
val, _ := math.Lgamma(p)
return val + corr + p - p*math.Log(p+q) + (q-0.5)*logrelerr(-p/(p+q))
}
return math.Log(math.Gamma(p) * (math.Gamma(q) / math.Gamma(p+q)))
}
// pbetaRaw is a part of pbeta for the beta distribution.
func pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y float64) float64 {
if q > 1.0 {
xb := p*math.Log(y) + q*math.Log(1.0-y) - logBeta(p, q) - math.Log(q)
ib := int(math.Max(xb/alnsml, 0.0))
term := math.Exp(xb - float64(ib)*alnsml)
c := 1.0 / (1.0 - y)
p1 := q * c / (p + q - 1.0)
finsum := 0.0
n := int(q)
if q == float64(n) {
n = n - 1
}
for i := 1; i <= n; i++ {
if p1 <= 1 && term/eps <= finsum {
break
}
xi := float64(i)
term = (q - xi + 1.0) * c * term / (p + q - xi)
if term > 1.0 {
ib = ib - 1
term = term * sml
}
if ib == 0 {
finsum = finsum + term
}
}
ans = ans + finsum
}
if y != x || p != pin {
ans = 1.0 - ans
}
ans = math.Max(math.Min(ans, 1.0), 0.0)
return ans
}
// pbeta returns distribution function of the beta distribution.
func pbeta(x, pin, qin float64) (ans float64) {
eps := d1mach(3)
alneps := math.Log(eps)
sml := d1mach(1)
alnsml := math.Log(sml)
y := x
p := pin
q := qin
if p/(p+q) < x {
y = 1.0 - y
p = qin
q = pin
}
if (p+q)*y/(p+1.0) < eps {
xb := p*math.Log(math.Max(y, sml)) - math.Log(p) - logBeta(p, q)
if xb > alnsml && y != 0.0 {
ans = math.Exp(xb)
}
if y != x || p != pin {
ans = 1.0 - ans
}
} else {
ps := q - math.Floor(q)
if ps == 0.0 {
ps = 1.0
}
xb := p*math.Log(y) - logBeta(ps, p) - math.Log(p)
if xb >= alnsml {
ans = math.Exp(xb)
term := ans * p
if ps != 1.0 {
n := int(math.Max(alneps/math.Log(y), 4.0))
for i := 1; i <= n; i++ {
xi := float64(i)
term = term * (xi - ps) * y / xi
ans = ans + term/(p+xi)
}
}
}
ans = pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y)
}
return ans
}
// betainvProbIterator is a part of betainv for the inverse of the beta
// function.
func betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBeta, maxCumulative, prob1, prob2 float64) float64 {
var i, j, prev, prop4 float64
j = 1
for prob := 0; prob < 1000; prob++ {
prop3 := pbeta(beta3, alpha1, beta1)
prop3 = (prop3 - prob1) * math.Exp(logBeta+prob2*math.Log(beta3)+beta2*math.Log(1.0-beta3))
if prop3*prop4 <= 0 {
prev = math.Max(math.Abs(j), maxCumulative)
}
h := 1.0
for iteratorCount := 0; iteratorCount < 1000; iteratorCount++ {
j = h * prop3
if math.Abs(j) < prev {
i = beta3 - j
if i >= 0 && i <= 1.0 {
if prev <= alpha3 {
return beta3
}
if math.Abs(prop3) <= alpha3 {
return beta3
}
if i != 0 && i != 1.0 {
break
}
}
}
h /= 3.0
}
if i == beta3 {
return beta3
}
beta3, prop4 = i, prop3
}
return beta3
}
// calcBetainv is an implementation for the quantile of the beta
// distribution.
func calcBetainv(probability, alpha, beta, lower, upper float64) float64 {
minCumulative, maxCumulative := 1.0e-300, 3.0e-308
lowerBound, upperBound := maxCumulative, 1.0-2.22e-16
needSwap := false
var alpha1, alpha2, beta1, beta2, beta3, prob1, x, y float64
if probability <= 0.5 {
prob1, alpha1, beta1 = probability, alpha, beta
} else {
prob1, alpha1, beta1, needSwap = 1.0-probability, beta, alpha, true
}
logBetaNum := logBeta(alpha, beta)
prob2 := math.Sqrt(-math.Log(prob1 * prob1))
prob3 := prob2 - (prob2*0.27061+2.3075)/(prob2*(prob2*0.04481+0.99229)+1)
if alpha1 > 1 && beta1 > 1 {
alpha2, beta2, prob2 = 1/(alpha1+alpha1-1), 1/(beta1+beta1-1), (prob3*prob3-3)/6
x = 2 / (alpha2 + beta2)
y = prob3*math.Sqrt(x+prob2)/x - (beta2-alpha2)*(prob2+5/6.0-2/(x*3))
beta3 = alpha1 / (alpha1 + beta1*math.Exp(y+y))
} else {
beta2, prob2 = 1/(beta1*9), beta1+beta1
beta2 = prob2 * math.Pow(1-beta2+prob3*math.Sqrt(beta2), 3)
if beta2 <= 0 {
beta3 = 1 - math.Exp((math.Log((1-prob1)*beta1)+logBetaNum)/beta1)
} else {
beta2 = (prob2 + alpha1*4 - 2) / beta2
if beta2 <= 1 {
beta3 = math.Exp((logBetaNum + math.Log(alpha1*prob1)) / alpha1)
} else {
beta3 = 1 - 2/(beta2+1)
}
}
}
beta2, prob2 = 1-beta1, 1-alpha1
if beta3 < lowerBound {
beta3 = lowerBound
} else if beta3 > upperBound {
beta3 = upperBound
}
alpha3 := math.Max(minCumulative, math.Pow(10.0, -13.0-2.5/(alpha1*alpha1)-0.5/(prob1*prob1)))
beta3 = betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBetaNum, maxCumulative, prob1, prob2)
if needSwap {
beta3 = 1.0 - beta3
}
return (upper-lower)*beta3 + lower
}
// betainv is an implementation of the formula functions BETAINV and
// BETA.INV.
func (fn *formulaFuncs) betainv(name string, argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name))
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 5 arguments", name))
}
probability := argsList.Front().Value.(formulaArg).ToNumber()
if probability.Type != ArgNumber {
return probability
}
if probability.Number <= 0 || probability.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
alpha := argsList.Front().Next().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
a, b := newNumberFormulaArg(0), newNumberFormulaArg(1)
if argsList.Len() > 3 {
if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber {
return a
}
}
if argsList.Len() == 5 {
if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber {
return b
}
}
if a.Number == b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(calcBetainv(probability.Number, alpha.Number, beta.Number, a.Number, b.Number))
}
// BETAINV function uses an iterative procedure to calculate the inverse of
// the cumulative beta probability density function for a supplied
// probability. The syntax of the function is:
//
// BETAINV(probability,alpha,beta,[A],[B])
func (fn *formulaFuncs) BETAINV(argsList *list.List) formulaArg {
return fn.betainv("BETAINV", argsList)
}
// BETAdotINV function uses an iterative procedure to calculate the inverse of
// the cumulative beta probability density function for a supplied
// probability. The syntax of the function is:
//
// BETA.INV(probability,alpha,beta,[A],[B])
func (fn *formulaFuncs) BETAdotINV(argsList *list.List) formulaArg {
return fn.betainv("BETA.INV", argsList)
}
// incompleteGamma is an implementation of the incomplete gamma function.
func incompleteGamma(a, x float64) float64 {
maxVal := 32
summer := 0.0
for n := 0; n <= maxVal; n++ {
divisor := a
for i := 1; i <= n; i++ {
divisor *= a + float64(i)
}
summer += math.Pow(x, float64(n)) / divisor
}
return math.Pow(x, a) * math.Exp(0-x) * summer
}
// binomCoeff implement binomial coefficient calculation.
func binomCoeff(n, k float64) float64 {
return fact(n) / (fact(k) * fact(n-k))
}
// binomdist implement binomial distribution calculation.
func binomdist(x, n, p float64) float64 {
return binomCoeff(n, x) * math.Pow(p, x) * math.Pow(1-p, n-x)
}
// BINOMdotDIST function returns the Binomial Distribution probability for a
// given number of successes from a specified number of trials. The syntax of
// the function is:
//
// BINOM.DIST(number_s,trials,probability_s,cumulative)
func (fn *formulaFuncs) BINOMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST requires 4 arguments")
}
return fn.BINOMDIST(argsList)
}
// BINOMDIST function returns the Binomial Distribution probability of a
// specified number of successes out of a specified number of trials. The
// syntax of the function is:
//
// BINOMDIST(number_s,trials,probability_s,cumulative)
func (fn *formulaFuncs) BINOMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOMDIST requires 4 arguments")
}
var s, trials, probability, cumulative formulaArg
if s = argsList.Front().Value.(formulaArg).ToNumber(); s.Type != ArgNumber {
return s
}
if trials = argsList.Front().Next().Value.(formulaArg).ToNumber(); trials.Type != ArgNumber {
return trials
}
if s.Number < 0 || s.Number > trials.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if probability = argsList.Back().Prev().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if probability.Number < 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if cumulative.Number == 1 {
bm := 0.0
for i := 0; i <= int(s.Number); i++ {
bm += binomdist(float64(i), trials.Number, probability.Number)
}
return newNumberFormulaArg(bm)
}
return newNumberFormulaArg(binomdist(s.Number, trials.Number, probability.Number))
}
// BINOMdotDISTdotRANGE function returns the Binomial Distribution probability
// for the number of successes from a specified number of trials falling into
// a specified range.
//
// BINOM.DIST.RANGE(trials,probability_s,number_s,[number_s2])
func (fn *formulaFuncs) BINOMdotDISTdotRANGE(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at least 3 arguments")
}
if argsList.Len() > 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at most 4 arguments")
}
trials := argsList.Front().Value.(formulaArg).ToNumber()
if trials.Type != ArgNumber {
return trials
}
probability := argsList.Front().Next().Value.(formulaArg).ToNumber()
if probability.Type != ArgNumber {
return probability
}
if probability.Number < 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
num1 := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if num1.Type != ArgNumber {
return num1
}
if num1.Number < 0 || num1.Number > trials.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
num2 := num1
if argsList.Len() > 3 {
if num2 = argsList.Back().Value.(formulaArg).ToNumber(); num2.Type != ArgNumber {
return num2
}
}
if num2.Number < 0 || num2.Number > trials.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
sum := 0.0
for i := num1.Number; i <= num2.Number; i++ {
sum += binomdist(i, trials.Number, probability.Number)
}
return newNumberFormulaArg(sum)
}
// binominv implement inverse of the binomial distribution calculation.
func binominv(n, p, alpha float64) float64 {
q, i, sum, maxVal := 1-p, 0.0, 0.0, 0.0
n = math.Floor(n)
if q > p {
factor := math.Pow(q, n)
sum = factor
for i = 0; i < n && sum < alpha; i++ {
factor *= (n - i) / (i + 1) * p / q
sum += factor
}
return i
}
factor := math.Pow(p, n)
sum, maxVal = 1-factor, n
for i = 0; i < maxVal && sum >= alpha; i++ {
factor *= (n - i) / (i + 1) * q / p
sum -= factor
}
return n - i
}
// BINOMdotINV function returns the inverse of the Cumulative Binomial
// Distribution. The syntax of the function is:
//
// BINOM.INV(trials,probability_s,alpha)
func (fn *formulaFuncs) BINOMdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOM.INV requires 3 numeric arguments")
}
trials := argsList.Front().Value.(formulaArg).ToNumber()
if trials.Type != ArgNumber {
return trials
}
if trials.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
probability := argsList.Front().Next().Value.(formulaArg).ToNumber()
if probability.Type != ArgNumber {
return probability
}
if probability.Number <= 0 || probability.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
alpha := argsList.Back().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
if alpha.Number <= 0 || alpha.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(binominv(trials.Number, probability.Number, alpha.Number))
}
// CHIDIST function calculates the right-tailed probability of the chi-square
// distribution. The syntax of the function is:
//
// CHIDIST(x,degrees_freedom)
func (fn *formulaFuncs) CHIDIST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHIDIST requires 2 numeric arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
degrees := argsList.Back().Value.(formulaArg).ToNumber()
if degrees.Type != ArgNumber {
return degrees
}
logSqrtPi, sqrtPi := math.Log(math.Sqrt(math.Pi)), 1/math.Sqrt(math.Pi)
var e, s, z, c, y float64
a, x1, even := x.Number/2, x.Number, int(degrees.Number)%2 == 0
if degrees.Number > 1 {
y = math.Exp(-a)
}
args := list.New()
args.PushBack(newNumberFormulaArg(-math.Sqrt(x1)))
o := fn.NORMSDIST(args)
s = 2 * o.Number
if even {
s = y
}
if degrees.Number > 2 {
x1 = (degrees.Number - 1) / 2
z = 0.5
if even {
z = 1
}
if a > 20 {
e = logSqrtPi
if even {
e = 0
}
c = math.Log(a)
for z <= x1 {
e = math.Log(z) + e
s += math.Exp(c*z - a - e)
z++
}
return newNumberFormulaArg(s)
}
e = sqrtPi / math.Sqrt(a)
if even {
e = 1
}
c = 0
for z <= x1 {
e = e * (a / z)
c = c + e
z++
}
return newNumberFormulaArg(c*y + s)
}
return newNumberFormulaArg(s)
}
// CHIINV function calculates the inverse of the right-tailed probability of
// the Chi-Square Distribution. The syntax of the function is:
//
// CHIINV(probability,deg_freedom)
func (fn *formulaFuncs) CHIINV(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHIINV requires 2 numeric arguments")
}
probability := argsList.Front().Value.(formulaArg).ToNumber()
if probability.Type != ArgNumber {
return probability
}
if probability.Number <= 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
deg := argsList.Back().Value.(formulaArg).ToNumber()
if deg.Type != ArgNumber {
return deg
}
if deg.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(gammainv(1-probability.Number, 0.5*deg.Number, 2.0))
}
// CHITEST function uses the chi-square test to calculate the probability that
// the differences between two supplied data sets (of observed and expected
// frequencies), are likely to be simply due to sampling error, or if they are
// likely to be real. The syntax of the function is:
//
// CHITEST(actual_range,expected_range)
func (fn *formulaFuncs) CHITEST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHITEST requires 2 arguments")
}
actual, expected := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg)
actualList, expectedList := actual.ToList(), expected.ToList()
rows := len(actual.Matrix)
if rows == 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
columns := len(actualList) / rows
if len(actualList) != len(expectedList) || len(actualList) == 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var result float64
var degrees int
for i := 0; i < len(actualList); i++ {
a, e := actualList[i].ToNumber(), expectedList[i].ToNumber()
if a.Type == ArgNumber && e.Type == ArgNumber {
if e.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
if e.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
result += (a.Number - e.Number) * (a.Number - e.Number) / e.Number
}
}
if rows == 1 {
degrees = columns - 1
} else if columns == 1 {
degrees = rows - 1
} else {
degrees = (columns - 1) * (rows - 1)
}
args := list.New()
args.PushBack(newNumberFormulaArg(result))
args.PushBack(newNumberFormulaArg(float64(degrees)))
return fn.CHIDIST(args)
}
// getGammaSeries calculates a power-series of the gamma function.
func getGammaSeries(fA, fX float64) float64 {
var (
fHalfMachEps = 2.22045e-016 / 2
fDenomfactor = fA
fSummand = 1 / fA
fSum = fSummand
nCount = 1
)
for fSummand/fSum > fHalfMachEps && nCount <= 10000 {
fDenomfactor = fDenomfactor + 1
fSummand = fSummand * fX / fDenomfactor
fSum = fSum + fSummand
nCount = nCount + 1
}
return fSum
}
// getGammaContFraction returns continued fraction with odd items of the gamma
// function.
func getGammaContFraction(fA, fX float64) float64 {
var (
fBigInv = 2.22045e-016
fHalfMachEps = fBigInv / 2
fBig = 1 / fBigInv
fCount = 0.0
fY = 1 - fA
fDenom = fX + 2 - fA
fPkm1 = fX + 1
fPkm2 = 1.0
fQkm1 = fDenom * fX
fQkm2 = fX
fApprox = fPkm1 / fQkm1
bFinished = false
)
for !bFinished && fCount < 10000 {
fCount = fCount + 1
fY = fY + 1
fDenom = fDenom + 2
var (
fNum = fY * fCount
f1 = fPkm1 * fDenom
f2 = fPkm2 * fNum
fPk = math.Nextafter(f1, f1) - math.Nextafter(f2, f2)
f3 = fQkm1 * fDenom
f4 = fQkm2 * fNum
fQk = math.Nextafter(f3, f3) - math.Nextafter(f4, f4)
)
if fQk != 0 {
fR := fPk / fQk
bFinished = math.Abs((fApprox-fR)/fR) <= fHalfMachEps
fApprox = fR
}
fPkm2, fPkm1, fQkm2, fQkm1 = fPkm1, fPk, fQkm1, fQk
if math.Abs(fPk) > fBig {
// reduce a fraction does not change the value
fPkm2 = fPkm2 * fBigInv
fPkm1 = fPkm1 * fBigInv
fQkm2 = fQkm2 * fBigInv
fQkm1 = fQkm1 * fBigInv
}
}
return fApprox
}
// getLogGammaHelper is a part of implementation of the function getLogGamma.
func getLogGammaHelper(fZ float64) float64 {
_fg := 6.024680040776729583740234375
zgHelp := fZ + _fg - 0.5
return math.Log(getLanczosSum(fZ)) + (fZ-0.5)*math.Log(zgHelp) - zgHelp
}
// getGammaHelper is a part of implementation of the function getLogGamma.
func getGammaHelper(fZ float64) float64 {
var (
gamma = getLanczosSum(fZ)
fg = 6.024680040776729583740234375
zgHelp = fZ + fg - 0.5
// avoid intermediate overflow
halfpower = math.Pow(zgHelp, fZ/2-0.25)
)
gamma *= halfpower
gamma /= math.Exp(zgHelp)
gamma *= halfpower
if fZ <= 20 && fZ == math.Floor(fZ) {
gamma = math.Round(gamma)
}
return gamma
}
// getLogGamma calculates the natural logarithm of the gamma function.
func getLogGamma(fZ float64) float64 {
fMaxGammaArgument := 171.624376956302
if fZ >= fMaxGammaArgument {
return getLogGammaHelper(fZ)
}
if fZ >= 1.0 {
return math.Log(getGammaHelper(fZ))
}
if fZ >= 0.5 {
return math.Log(getGammaHelper(fZ+1) / fZ)
}
return getLogGammaHelper(fZ+2) - math.Log(fZ+1) - math.Log(fZ)
}
// getLowRegIGamma returns lower regularized incomplete gamma function.
func getLowRegIGamma(fA, fX float64) float64 {
lnFactor := fA*math.Log(fX) - fX - getLogGamma(fA)
factor := math.Exp(lnFactor)
if fX > fA+1 {
return 1 - factor*getGammaContFraction(fA, fX)
}
return factor * getGammaSeries(fA, fX)
}
// getChiSqDistCDF returns left tail for the Chi-Square distribution.
func getChiSqDistCDF(fX, fDF float64) float64 {
if fX <= 0 {
return 0
}
return getLowRegIGamma(fDF/2, fX/2)
}
// getChiSqDistPDF calculates the probability density function for the
// Chi-Square distribution.
func getChiSqDistPDF(fX, fDF float64) float64 {
if fDF*fX > 1391000 {
return math.Exp((0.5*fDF-1)*math.Log(fX*0.5) - 0.5*fX - math.Log(2) - getLogGamma(0.5*fDF))
}
var fCount, fValue float64
if math.Mod(fDF, 2) < 0.5 {
fValue = 0.5
fCount = 2
} else {
fValue = 1 / math.Sqrt(fX*2*math.Pi)
fCount = 1
}
for fCount < fDF {
fValue *= fX / fCount
fCount += 2
}
if fX >= 1425 {
fValue = math.Exp(math.Log(fValue) - fX/2)
} else {
fValue *= math.Exp(-fX / 2)
}
return fValue
}
// CHISQdotDIST function calculates the Probability Density Function or the
// Cumulative Distribution Function for the Chi-Square Distribution. The
// syntax of the function is:
//
// CHISQ.DIST(x,degrees_freedom,cumulative)
func (fn *formulaFuncs) CHISQdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST requires 3 arguments")
}
var x, degrees, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if x.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
maxDeg := math.Pow10(10)
if degrees.Number < 1 || degrees.Number >= maxDeg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(getChiSqDistCDF(x.Number, degrees.Number))
}
return newNumberFormulaArg(getChiSqDistPDF(x.Number, degrees.Number))
}
// CHISQdotDISTdotRT function calculates the right-tailed probability of the
// Chi-Square Distribution. The syntax of the function is:
//
// CHISQ.DIST.RT(x,degrees_freedom)
func (fn *formulaFuncs) CHISQdotDISTdotRT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST.RT requires 2 numeric arguments")
}
return fn.CHIDIST(argsList)
}
// CHISQdotTEST function performs the chi-square test on two supplied data sets
// (of observed and expected frequencies), and returns the probability that
// the differences between the sets are simply due to sampling error. The
// syntax of the function is:
//
// CHISQ.TEST(actual_range,expected_range)
func (fn *formulaFuncs) CHISQdotTEST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.TEST requires 2 arguments")
}
return fn.CHITEST(argsList)
}
// hasChangeOfSign check if the sign has been changed.
func hasChangeOfSign(u, w float64) bool {
return (u < 0 && w > 0) || (u > 0 && w < 0)
}
// calcInverseIterator directly maps the required parameters for inverse
// distribution functions.
type calcInverseIterator struct {
name string
fp, fDF, nT float64
}
// callBack implements the callback function for the inverse iterator.
func (iterator *calcInverseIterator) callBack(x float64) float64 {
if iterator.name == "CHISQ.INV" {
return iterator.fp - getChiSqDistCDF(x, iterator.fDF)
}
return iterator.fp - getTDist(x, iterator.fDF, iterator.nT)
}
// inverseQuadraticInterpolation inverse quadratic interpolation with
// additional brackets.
func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx, fBy float64) float64 {
fYEps := 1.0e-307
fXEps := 2.22045e-016
fPx, fPy, fQx, fQy, fRx, fRy := fAx, fAy, fBx, fBy, fAx, fAy
fSx := 0.5 * (fAx + fBx)
bHasToInterpolate := true
nCount := 0
for nCount < 500 && math.Abs(fRy) > fYEps && (fBx-fAx) > math.Max(math.Abs(fAx), math.Abs(fBx))*fXEps {
if bHasToInterpolate {
if fPy != fQy && fQy != fRy && fRy != fPy {
fSx = fPx*fRy*fQy/(fRy-fPy)/(fQy-fPy) + fRx*fQy*fPy/(fQy-fRy)/(fPy-fRy) +
fQx*fPy*fRy/(fPy-fQy)/(fRy-fQy)
bHasToInterpolate = (fAx < fSx) && (fSx < fBx)
} else {
bHasToInterpolate = false
}
}
if !bHasToInterpolate {
fSx = 0.5 * (fAx + fBx)
fQx, fQy = fBx, fBy
bHasToInterpolate = true
}
fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy
fRy = iterator.callBack(fSx)
if hasChangeOfSign(fAy, fRy) {
fBx, fBy = fRx, fRy
} else {
fAx, fAy = fRx, fRy
}
bHasToInterpolate = bHasToInterpolate && (math.Abs(fRy)*2 <= math.Abs(fQy))
nCount++
}
return fRx
}
// calcIterateInverse function calculates the iteration for inverse
// distributions.
func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 {
fAy, fBy := iterator.callBack(fAx), iterator.callBack(fBx)
var fTemp float64
var nCount int
for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ {
if math.Abs(fAy) <= math.Abs(fBy) {
fTemp = fAx
fAx += 2 * (fAx - fBx)
if fAx < 0 {
fAx = 0
}
fBx = fTemp
fBy = fAy
fAy = iterator.callBack(fAx)
} else {
fTemp = fBx
fBx += 2 * (fBx - fAx)
fAx = fTemp
fAy = fBy
fBy = iterator.callBack(fBx)
}
}
if fAy == 0 || fBy == 0 {
return 0
}
return inverseQuadraticInterpolation(iterator, fAx, fAy, fBx, fBy)
}
// CHISQdotINV function calculates the inverse of the left-tailed probability
// of the Chi-Square Distribution. The syntax of the function is:
//
// CHISQ.INV(probability,degrees_freedom)
func (fn *formulaFuncs) CHISQdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV requires 2 numeric arguments")
}
var probability, degrees formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if probability.Number < 0 || probability.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if degrees.Number < 1 || degrees.Number > math.Pow10(10) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{
name: "CHISQ.INV",
fp: probability.Number,
fDF: degrees.Number,
}, degrees.Number/2, degrees.Number))
}
// CHISQdotINVdotRT function calculates the inverse of the right-tailed
// probability of the Chi-Square Distribution. The syntax of the function is:
//
// CHISQ.INV.RT(probability,degrees_freedom)
func (fn *formulaFuncs) CHISQdotINVdotRT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV.RT requires 2 numeric arguments")
}
return fn.CHIINV(argsList)
}
// confidence is an implementation of the formula functions CONFIDENCE and
// CONFIDENCE.NORM.
func (fn *formulaFuncs) confidence(name string, argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 numeric arguments", name))
}
alpha := argsList.Front().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
if alpha.Number <= 0 || alpha.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
stdDev := argsList.Front().Next().Value.(formulaArg).ToNumber()
if stdDev.Type != ArgNumber {
return stdDev
}
if stdDev.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
size := argsList.Back().Value.(formulaArg).ToNumber()
if size.Type != ArgNumber {
return size
}
if size.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New()
args.Init()
args.PushBack(newNumberFormulaArg(alpha.Number / 2))
args.PushBack(newNumberFormulaArg(0))
args.PushBack(newNumberFormulaArg(1))
return newNumberFormulaArg(-fn.NORMINV(args).Number * (stdDev.Number / math.Sqrt(size.Number)))
}
// CONFIDENCE function uses a Normal Distribution to calculate a confidence
// value that can be used to construct the Confidence Interval for a
// population mean, for a supplied probability and sample size. It is assumed
// that the standard deviation of the population is known. The syntax of the
// function is:
//
// CONFIDENCE(alpha,standard_dev,size)
func (fn *formulaFuncs) CONFIDENCE(argsList *list.List) formulaArg {
return fn.confidence("CONFIDENCE", argsList)
}
// CONFIDENCEdotNORM function uses a Normal Distribution to calculate a
// confidence value that can be used to construct the confidence interval for
// a population mean, for a supplied probability and sample size. It is
// assumed that the standard deviation of the population is known. The syntax
// of the function is:
//
// CONFIDENCE.NORM(alpha,standard_dev,size)
func (fn *formulaFuncs) CONFIDENCEdotNORM(argsList *list.List) formulaArg {
return fn.confidence("CONFIDENCE.NORM", argsList)
}
// CONFIDENCEdotT function uses a Student's T-Distribution to calculate a
// confidence value that can be used to construct the confidence interval for
// a population mean, for a supplied probablity and supplied sample size. It
// is assumed that the standard deviation of the population is known. The
// syntax of the function is:
//
// CONFIDENCE.T(alpha,standard_dev,size)
func (fn *formulaFuncs) CONFIDENCEdotT(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "CONFIDENCE.T requires 3 arguments")
}
var alpha, standardDev, size formulaArg
if alpha = argsList.Front().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber {
return alpha
}
if standardDev = argsList.Front().Next().Value.(formulaArg).ToNumber(); standardDev.Type != ArgNumber {
return standardDev
}
if size = argsList.Back().Value.(formulaArg).ToNumber(); size.Type != ArgNumber {
return size
}
if alpha.Number <= 0 || alpha.Number >= 1 || standardDev.Number <= 0 || size.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if size.Number == 1 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(standardDev.Number * calcIterateInverse(calcInverseIterator{
name: "CONFIDENCE.T",
fp: alpha.Number,
fDF: size.Number - 1,
nT: 2,
}, size.Number/2, size.Number) / math.Sqrt(size.Number))
}
// covar is an implementation of the formula functions COVAR, COVARIANCE.P and
// COVARIANCE.S.
func (fn *formulaFuncs) covar(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
}
array1 := argsList.Front().Value.(formulaArg)
array2 := argsList.Back().Value.(formulaArg)
left, right := array1.ToList(), array2.ToList()
n := len(left)
if n != len(right) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
l1, l2 := list.New(), list.New()
l1.PushBack(array1)
l2.PushBack(array2)
result, skip := 0.0, 0
mean1, mean2 := fn.AVERAGE(l1), fn.AVERAGE(l2)
for i := 0; i < n; i++ {
arg1 := left[i].ToNumber()
arg2 := right[i].ToNumber()
if arg1.Type == ArgError || arg2.Type == ArgError {
skip++
continue
}
result += (arg1.Number - mean1.Number) * (arg2.Number - mean2.Number)
}
if name == "COVARIANCE.S" {
return newNumberFormulaArg(result / float64(n-skip-1))
}
return newNumberFormulaArg(result / float64(n-skip))
}
// COVAR function calculates the covariance of two supplied sets of values. The
// syntax of the function is:
//
// COVAR(array1,array2)
func (fn *formulaFuncs) COVAR(argsList *list.List) formulaArg {
return fn.covar("COVAR", argsList)
}
// COVARIANCEdotP function calculates the population covariance of two supplied
// sets of values. The syntax of the function is:
//
// COVARIANCE.P(array1,array2)
func (fn *formulaFuncs) COVARIANCEdotP(argsList *list.List) formulaArg {
return fn.covar("COVARIANCE.P", argsList)
}
// COVARIANCEdotS function calculates the sample covariance of two supplied
// sets of values. The syntax of the function is:
//
// COVARIANCE.S(array1,array2)
func (fn *formulaFuncs) COVARIANCEdotS(argsList *list.List) formulaArg {
return fn.covar("COVARIANCE.S", argsList)
}
// calcStringCountSum is part of the implementation countSum.
func calcStringCountSum(countText bool, count, sum float64, num, arg formulaArg) (float64, float64) {
if countText && num.Type == ArgError && arg.String != "" {
count++
}
if num.Type == ArgNumber {
sum += num.Number
count++
}
return count, sum
}
// countSum get count and sum for a formula arguments array.
func (fn *formulaFuncs) countSum(countText bool, args []formulaArg) (count, sum float64) {
for _, arg := range args {
switch arg.Type {
case ArgNumber:
if countText || !arg.Boolean {
sum += arg.Number
count++
}
case ArgString:
if !countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
continue
} else if countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
num := arg.ToBool()
if num.Type == ArgNumber {
count++
sum += num.Number
continue
}
}
num := arg.ToNumber()
count, sum = calcStringCountSum(countText, count, sum, num, arg)
case ArgList, ArgMatrix:
cnt, summary := fn.countSum(countText, arg.ToList())
sum += summary
count += cnt
}
}
return
}
// CORREL function calculates the Pearson Product-Moment Correlation
// Coefficient for two sets of values. The syntax of the function is:
//
// CORREL(array1,array2)
func (fn *formulaFuncs) CORREL(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CORREL requires 2 arguments")
}
array1 := argsList.Front().Value.(formulaArg)
array2 := argsList.Back().Value.(formulaArg)
left, right := array1.ToList(), array2.ToList()
n := len(left)
if n != len(right) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
l1, l2, l3 := list.New(), list.New(), list.New()
for i := 0; i < n; i++ {
if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 {
l1.PushBack(lhs)
l2.PushBack(rhs)
}
}
stdev1, stdev2 := fn.STDEV(l1), fn.STDEV(l2)
if stdev1.Number == 0 || stdev2.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
mean1, mean2, skip := fn.AVERAGE(l1), fn.AVERAGE(l2), 0
for i := 0; i < n; i++ {
lhs, rhs := left[i].ToNumber(), right[i].ToNumber()
if lhs.Number == 0 || rhs.Number == 0 {
skip++
continue
}
l3.PushBack(newNumberFormulaArg((lhs.Number - mean1.Number) * (rhs.Number - mean2.Number)))
}
return newNumberFormulaArg(fn.SUM(l3).Number / float64(n-skip-1) / stdev1.Number / stdev2.Number)
}
// COUNT function returns the count of numeric values in a supplied set of
// cells or values. This count includes both numbers and dates. The syntax of
// the function is:
//
// COUNT(value1,[value2],...)
func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg {
var count int
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
if num := arg.ToNumber(); num.Type == ArgNumber {
count++
}
case ArgNumber:
count++
case ArgMatrix:
for _, row := range arg.Matrix {
for _, cell := range row {
if cell.Type == ArgNumber {
count++
}
}
}
}
}
return newNumberFormulaArg(float64(count))
}
// COUNTA function returns the number of non-blanks within a supplied set of
// cells or values. The syntax of the function is:
//
// COUNTA(value1,[value2],...)
func (fn *formulaFuncs) COUNTA(argsList *list.List) formulaArg {
var count int
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
if arg.String != "" {
count++
}
case ArgNumber:
count++
case ArgMatrix:
for _, row := range arg.ToList() {
switch row.Type {
case ArgString:
if row.String != "" {
count++
}
case ArgNumber:
count++
}
}
}
}
return newNumberFormulaArg(float64(count))
}
// COUNTBLANK function returns the number of blank cells in a supplied range.
// The syntax of the function is:
//
// COUNTBLANK(range)
func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COUNTBLANK requires 1 argument")
}
var count float64
for _, cell := range argsList.Front().Value.(formulaArg).ToList() {
if cell.Type == ArgEmpty {
count++
}
}
return newNumberFormulaArg(count)
}
// COUNTIF function returns the number of cells within a supplied range, that
// satisfy a given criteria. The syntax of the function is:
//
// COUNTIF(range,criteria)
func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "COUNTIF requires 2 arguments")
}
var (
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
count float64
)
for _, cell := range argsList.Front().Value.(formulaArg).ToList() {
if cell.Type == ArgString && criteria.Condition.Type != ArgString {
continue
}
if ok, _ := formulaCriteriaEval(cell, criteria); ok {
count++
}
}
return newNumberFormulaArg(count)
}
// formulaIfsMatch function returns cells reference array which match criteria.
func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) {
for i := 0; i < len(args)-1; i += 2 {
var match []cellRef
matrix, criteria := args[i].Matrix, formulaCriteriaParser(args[i+1])
if i == 0 {
for rowIdx, row := range matrix {
for colIdx, col := range row {
if ok, _ := formulaCriteriaEval(col, criteria); ok {
match = append(match, cellRef{Col: colIdx, Row: rowIdx})
}
}
}
} else {
match = []cellRef{}
for _, ref := range cellRefs {
value := matrix[ref.Row][ref.Col]
if ok, _ := formulaCriteriaEval(value, criteria); ok {
match = append(match, ref)
}
}
}
cellRefs = match[:]
}
return
}
// COUNTIFS function returns the number of rows within a table, that satisfy a
// set of given criteria. The syntax of the function is:
//
// COUNTIFS(criteria_range1,criteria1,[criteria_range2,criteria2],...)
func (fn *formulaFuncs) COUNTIFS(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "COUNTIFS requires at least 2 arguments")
}
if argsList.Len()%2 != 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var args []formulaArg
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
return newNumberFormulaArg(float64(len(formulaIfsMatch(args))))
}
// CRITBINOM function returns the inverse of the Cumulative Binomial
// Distribution. I.e. for a specific number of independent trials, the
// function returns the smallest value (number of successes) for which the
// cumulative binomial distribution is greater than or equal to a specified
// value. The syntax of the function is:
//
// CRITBINOM(trials,probability_s,alpha)
func (fn *formulaFuncs) CRITBINOM(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "CRITBINOM requires 3 numeric arguments")
}
return fn.BINOMdotINV(argsList)
}
// DEVSQ function calculates the sum of the squared deviations from the sample
// mean. The syntax of the function is:
//
// DEVSQ(number1,[number2],...)
func (fn *formulaFuncs) DEVSQ(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DEVSQ requires at least 1 numeric argument")
}
avg, count, result := fn.AVERAGE(argsList), -1, 0.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
for _, cell := range arg.Value.(formulaArg).ToList() {
if cell.Type != ArgNumber {
continue
}
count++
if count == 0 {
result = math.Pow(cell.Number-avg.Number, 2)
continue
}
result += math.Pow(cell.Number-avg.Number, 2)
}
}
if count == -1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(result)
}
// FISHER function calculates the Fisher Transformation for a supplied value.
// The syntax of the function is:
//
// FISHER(x)
func (fn *formulaFuncs) FISHER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgString:
arg := token.ToNumber()
if arg.Type == ArgNumber {
if arg.Number <= -1 || arg.Number >= 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(0.5 * math.Log((1+arg.Number)/(1-arg.Number)))
}
case ArgNumber:
if token.Number <= -1 || token.Number >= 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(0.5 * math.Log((1+token.Number)/(1-token.Number)))
}
return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
}
// FISHERINV function calculates the inverse of the Fisher Transformation and
// returns a value between -1 and +1. The syntax of the function is:
//
// FISHERINV(y)
func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgString:
arg := token.ToNumber()
if arg.Type == ArgNumber {
return newNumberFormulaArg((math.Exp(2*arg.Number) - 1) / (math.Exp(2*arg.Number) + 1))
}
case ArgNumber:
return newNumberFormulaArg((math.Exp(2*token.Number) - 1) / (math.Exp(2*token.Number) + 1))
}
return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
}
// FORECAST function predicts a future point on a linear trend line fitted to a
// supplied set of x- and y- values. The syntax of the function is:
//
// FORECAST(x,known_y's,known_x's)
func (fn *formulaFuncs) FORECAST(argsList *list.List) formulaArg {
return fn.pearsonProduct("FORECAST", 3, argsList)
}
// FORECASTdotLINEAR function predicts a future point on a linear trend line
// fitted to a supplied set of x- and y- values. The syntax of the function is:
//
// FORECAST.LINEAR(x,known_y's,known_x's)
func (fn *formulaFuncs) FORECASTdotLINEAR(argsList *list.List) formulaArg {
return fn.pearsonProduct("FORECAST.LINEAR", 3, argsList)
}
// matrixToSortedColumnList convert matrix formula arguments to a ascending
// order list by column.
func matrixToSortedColumnList(arg formulaArg) formulaArg {
var (
mtx []formulaArg
cols = len(arg.Matrix[0])
)
for colIdx := 0; colIdx < cols; colIdx++ {
for _, row := range arg.Matrix {
cell := row[colIdx]
if cell.Type == ArgError {
return cell
}
if cell.Type == ArgNumber {
mtx = append(mtx, cell)
}
}
}
argsList := newListFormulaArg(mtx)
sort.Slice(argsList.List, func(i, j int) bool {
return argsList.List[i].Number < argsList.List[j].Number
})
return argsList
}
// FREQUENCY function to count how many children fall into different age
// ranges. The syntax of the function is:
//
// FREQUENCY(data_array,bins_array)
func (fn *formulaFuncs) FREQUENCY(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "FREQUENCY requires 2 arguments")
}
data, bins := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg)
if len(data.Matrix) == 0 {
data.Matrix = [][]formulaArg{{data}}
}
if len(bins.Matrix) == 0 {
bins.Matrix = [][]formulaArg{{bins}}
}
var (
dataMtx, binsMtx formulaArg
c [][]formulaArg
i, j int
)
if dataMtx = matrixToSortedColumnList(data); dataMtx.Type != ArgList {
return dataMtx
}
if binsMtx = matrixToSortedColumnList(bins); binsMtx.Type != ArgList {
return binsMtx
}
for row := 0; row < len(binsMtx.List)+1; row++ {
var rows []formulaArg
for col := 0; col < 1; col++ {
rows = append(rows, newNumberFormulaArg(0))
}
c = append(c, rows)
}
for j = 0; j < len(binsMtx.List); j++ {
n := 0.0
for i < len(dataMtx.List) && dataMtx.List[i].Number <= binsMtx.List[j].Number {
n++
i++
}
c[j] = []formulaArg{newNumberFormulaArg(n)}
}
c[j] = []formulaArg{newNumberFormulaArg(float64(len(dataMtx.List) - i))}
if len(c) > 2 {
c[1], c[2] = c[2], c[1]
}
return newMatrixFormulaArg(c)
}
// GAMMA function returns the value of the Gamma Function, Γ(n), for a
// specified number, n. The syntax of the function is:
//
// GAMMA(number)
func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
}
if number.Number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(math.Gamma(number.Number))
}
// GAMMAdotDIST function returns the Gamma Distribution, which is frequently
// used to provide probabilities for values that may have a skewed
// distribution, such as queuing analysis.
//
// GAMMA.DIST(x,alpha,beta,cumulative)
func (fn *formulaFuncs) GAMMAdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.DIST requires 4 arguments")
}
return fn.GAMMADIST(argsList)
}
// GAMMADIST function returns the Gamma Distribution, which is frequently used
// to provide probabilities for values that may have a skewed distribution,
// such as queuing analysis.
//
// GAMMADIST(x,alpha,beta,cumulative)
func (fn *formulaFuncs) GAMMADIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMADIST requires 4 arguments")
}
var x, alpha, beta, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if x.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber {
return alpha
}
if beta = argsList.Back().Prev().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if cumulative.Number == 1 {
return newNumberFormulaArg(incompleteGamma(alpha.Number, x.Number/beta.Number) / math.Gamma(alpha.Number))
}
return newNumberFormulaArg((1 / (math.Pow(beta.Number, alpha.Number) * math.Gamma(alpha.Number))) * math.Pow(x.Number, alpha.Number-1) * math.Exp(0-(x.Number/beta.Number)))
}
// gammainv returns the inverse of the Gamma distribution for the specified
// value.
func gammainv(probability, alpha, beta float64) float64 {
xLo, xHi := 0.0, alpha*beta*5
dx, x, xNew, result := 1024.0, 1.0, 1.0, 0.0
for i := 0; math.Abs(dx) > 8.88e-016 && i <= 256; i++ {
result = incompleteGamma(alpha, x/beta) / math.Gamma(alpha)
e := result - probability
if e == 0 {
dx = 0
} else if e < 0 {
xLo = x
} else {
xHi = x
}
pdf := (1 / (math.Pow(beta, alpha) * math.Gamma(alpha))) * math.Pow(x, alpha-1) * math.Exp(0-(x/beta))
if pdf != 0 {
dx = e / pdf
xNew = x - dx
}
if xNew < xLo || xNew > xHi || pdf == 0 {
xNew = (xLo + xHi) / 2
dx = xNew - x
}
x = xNew
}
return x
}
// GAMMAdotINV function returns the inverse of the Gamma Cumulative
// Distribution. The syntax of the function is:
//
// GAMMA.INV(probability,alpha,beta)
func (fn *formulaFuncs) GAMMAdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.INV requires 3 arguments")
}
return fn.GAMMAINV(argsList)
}
// GAMMAINV function returns the inverse of the Gamma Cumulative Distribution.
// The syntax of the function is:
//
// GAMMAINV(probability,alpha,beta)
func (fn *formulaFuncs) GAMMAINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMAINV requires 3 arguments")
}
var probability, alpha, beta formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if probability.Number < 0 || probability.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber {
return alpha
}
if beta = argsList.Back().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(gammainv(probability.Number, alpha.Number, beta.Number))
}
// GAMMALN function returns the natural logarithm of the Gamma Function, Γ
// (n). The syntax of the function is:
//
// GAMMALN(x)
func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
}
if x.Number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(math.Log(math.Gamma(x.Number)))
}
// GAMMALNdotPRECISE function returns the natural logarithm of the Gamma
// Function, Γ(n). The syntax of the function is:
//
// GAMMALN.PRECISE(x)
func (fn *formulaFuncs) GAMMALNdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN.PRECISE requires 1 numeric argument")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
if x.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(getLogGamma(x.Number))
}
// GAUSS function returns the probability that a member of a standard normal
// population will fall between the mean and a specified number of standard
// deviations from the mean. The syntax of the function is:
//
// GAUSS(z)
func (fn *formulaFuncs) GAUSS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GAUSS requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
args.PushBack(newBoolFormulaArg(true))
normdist := fn.NORMDIST(args)
if normdist.Type != ArgNumber {
return normdist
}
return newNumberFormulaArg(normdist.Number - 0.5)
}
// GEOMEAN function calculates the geometric mean of a supplied set of values.
// The syntax of the function is:
//
// GEOMEAN(number1,[number2],...)
func (fn *formulaFuncs) GEOMEAN(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "GEOMEAN requires at least 1 numeric argument")
}
product := fn.PRODUCT(argsList)
if product.Type != ArgNumber {
return product
}
count := fn.COUNT(argsList)
minVal := fn.MIN(argsList)
if product.Number > 0 && minVal.Number > 0 {
return newNumberFormulaArg(math.Pow(product.Number, 1/count.Number))
}
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
// getNewMatrix create matrix by given columns and rows.
func getNewMatrix(c, r int) (matrix [][]float64) {
for i := 0; i < c; i++ {
for j := 0; j < r; j++ {
for x := len(matrix); x <= i; x++ {
matrix = append(matrix, []float64{})
}
for y := len(matrix[i]); y <= j; y++ {
matrix[i] = append(matrix[i], 0)
}
matrix[i][j] = 0
}
}
return
}
// approxSub subtract two values, if signs are identical and the values are
// equal, will be returns 0 instead of calculating the subtraction.
func approxSub(a, b float64) float64 {
if ((a < 0 && b < 0) || (a > 0 && b > 0)) && math.Abs(a-b) < 2.22045e-016 {
return 0
}
return a - b
}
// matrixClone return a copy of all elements of the original matrix.
func matrixClone(matrix [][]float64) (cloneMatrix [][]float64) {
for i := 0; i < len(matrix); i++ {
for j := 0; j < len(matrix[i]); j++ {
for x := len(cloneMatrix); x <= i; x++ {
cloneMatrix = append(cloneMatrix, []float64{})
}
for k := len(cloneMatrix[i]); k <= j; k++ {
cloneMatrix[i] = append(cloneMatrix[i], 0)
}
cloneMatrix[i][j] = matrix[i][j]
}
}
return
}
// trendGrowthMatrixInfo defined matrix checking result.
type trendGrowthMatrixInfo struct {
trendType, nCX, nCY, nRX, nRY, M, N int
mtxX, mtxY [][]float64
}
// prepareTrendGrowthMtxX is a part of implementation of the trend growth prepare.
func prepareTrendGrowthMtxX(mtxX [][]float64) [][]float64 {
var mtx [][]float64
for i := 0; i < len(mtxX); i++ {
for j := 0; j < len(mtxX[i]); j++ {
if mtxX[i][j] == 0 {
return nil
}
for x := len(mtx); x <= j; x++ {
mtx = append(mtx, []float64{})
}
for y := len(mtx[j]); y <= i; y++ {
mtx[j] = append(mtx[j], 0)
}
mtx[j][i] = mtxX[i][j]
}
}
return mtx
}
// prepareTrendGrowthMtxY is a part of implementation of the trend growth prepare.
func prepareTrendGrowthMtxY(bLOG bool, mtxY [][]float64) [][]float64 {
var mtx [][]float64
for i := 0; i < len(mtxY); i++ {
for j := 0; j < len(mtxY[i]); j++ {
if mtxY[i][j] == 0 {
return nil
}
for x := len(mtx); x <= j; x++ {
mtx = append(mtx, []float64{})
}
for y := len(mtx[j]); y <= i; y++ {
mtx[j] = append(mtx[j], 0)
}
mtx[j][i] = mtxY[i][j]
}
}
if bLOG {
var pNewY [][]float64
for i := 0; i < len(mtxY); i++ {
for j := 0; j < len(mtxY[i]); j++ {
fVal := mtxY[i][j]
if fVal <= 0 {
return nil
}
for x := len(pNewY); x <= j; x++ {
pNewY = append(pNewY, []float64{})
}
for y := len(pNewY[j]); y <= i; y++ {
pNewY[j] = append(pNewY[j], 0)
}
pNewY[j][i] = math.Log(fVal)
}
}
mtx = pNewY
}
return mtx
}
// prepareTrendGrowth check and return the result.
func prepareTrendGrowth(bLOG bool, mtxX, mtxY [][]float64) (*trendGrowthMatrixInfo, formulaArg) {
var nCX, nRX, M, N, trendType int
nRY, nCY := len(mtxY), len(mtxY[0])
cntY := nCY * nRY
newY := prepareTrendGrowthMtxY(bLOG, mtxY)
if newY == nil {
return nil, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
var newX [][]float64
if len(mtxX) != 0 {
nRX, nCX = len(mtxX), len(mtxX[0])
if newX = prepareTrendGrowthMtxX(mtxX); newX == nil {
return nil, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if nCX == nCY && nRX == nRY {
trendType, M, N = 1, 1, cntY // simple regression
} else if nCY != 1 && nRY != 1 {
return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
} else if nCY == 1 {
if nRX != nRY {
return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
}
trendType, M, N = 2, nCX, nRY
} else if nCX != nCY {
return nil, newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
} else {
trendType, M, N = 3, nRX, nCY
}
} else {
newX = getNewMatrix(nCY, nRY)
nCX, nRX = nCY, nRY
num := 1.0
for i := 0; i < nRY; i++ {
for j := 0; j < nCY; j++ {
newX[j][i] = num
num++
}
}
trendType, M, N = 1, 1, cntY
}
return &trendGrowthMatrixInfo{
trendType: trendType,
nCX: nCX,
nCY: nCY,
nRX: nRX,
nRY: nRY,
M: M,
N: N,
mtxX: newX,
mtxY: newY,
}, newEmptyFormulaArg()
}
// calcPosition calculate position for matrix by given index.
func calcPosition(mtx [][]float64, idx int) (row, col int) {
rowSize := len(mtx[0])
col = idx
if rowSize > 1 {
col = idx / rowSize
}
row = idx - col*rowSize
return
}
// getDouble returns float64 data type value in the matrix by given index.
func getDouble(mtx [][]float64, idx int) float64 {
row, col := calcPosition(mtx, idx)
return mtx[col][row]
}
// putDouble set a float64 data type value in the matrix by given index.
func putDouble(mtx [][]float64, idx int, val float64) {
row, col := calcPosition(mtx, idx)
mtx[col][row] = val
}
// calcMeanOverAll returns mean of the given matrix by over all element.
func calcMeanOverAll(mtx [][]float64, n int) float64 {
var sum float64
for i := 0; i < len(mtx); i++ {
for j := 0; j < len(mtx[i]); j++ {
sum += mtx[i][j]
}
}
return sum / float64(n)
}
// calcSumProduct returns uses the matrices as vectors of length M over all
// element.
func calcSumProduct(mtxA, mtxB [][]float64, m int) float64 {
sum := 0.0
for i := 0; i < m; i++ {
sum += getDouble(mtxA, i) * getDouble(mtxB, i)
}
return sum
}
// calcColumnMeans calculates means of the columns of matrix.
func calcColumnMeans(mtxX, mtxRes [][]float64, c, r int) {
for i := 0; i < c; i++ {
var sum float64
for k := 0; k < r; k++ {
sum += mtxX[i][k]
}
putDouble(mtxRes, i, sum/float64(r))
}
}
// calcColumnsDelta calculates subtract of the columns of matrix.
func calcColumnsDelta(mtx, columnMeans [][]float64, c, r int) {
for i := 0; i < c; i++ {
for k := 0; k < r; k++ {
mtx[i][k] = approxSub(mtx[i][k], getDouble(columnMeans, i))
}
}
}
// calcSign returns sign by given value, no mathematical signum, but used to
// switch between adding and subtracting.
func calcSign(val float64) float64 {
if val > 0 {
return 1
}
return -1
}
// calcColsMaximumNorm is a special version for use within QR
// decomposition. Maximum norm of column index c starting in row index r;
// matrix A has count n rows.
func calcColsMaximumNorm(mtxA [][]float64, c, r, n int) float64 {
var norm float64
for row := r; row < n; row++ {
if norm < math.Abs(mtxA[c][row]) {
norm = math.Abs(mtxA[c][row])
}
}
return norm
}
// calcFastMult returns multiply n x m matrix A with m x l matrix B to n x l matrix R.
func calcFastMult(mtxA, mtxB, mtxR [][]float64, n, m, l int) {
var sum float64
for row := 0; row < n; row++ {
for col := 0; col < l; col++ {
sum = 0.0
for k := 0; k < m; k++ {
sum += mtxA[k][row] * mtxB[col][k]
}
mtxR[col][row] = sum
}
}
}
// calcRowsEuclideanNorm is a special version for use within QR
// decomposition. Euclidean norm of column index c starting in row index r;
// matrix a has count n rows.
func calcRowsEuclideanNorm(mtxA [][]float64, c, r, n int) float64 {
var norm float64
for row := r; row < n; row++ {
norm += mtxA[c][row] * mtxA[c][row]
}
return math.Sqrt(norm)
}
// calcRowsSumProduct is a special version for use within QR decomposition.
// starting in row index r;
// a and b are indices of columns, matrices A and B have count n rows.
func calcRowsSumProduct(mtxA [][]float64, a int, mtxB [][]float64, b, r, n int) float64 {
var result float64
for row := r; row < n; row++ {
result += mtxA[a][row] * mtxB[b][row]
}
return result
}
// calcSolveWithUpperRightTriangle solve for X in R*X=S using back substitution.
func calcSolveWithUpperRightTriangle(mtxA [][]float64, vecR []float64, mtxS [][]float64, k int, bIsTransposed bool) {
var row int
for rowp1 := k; rowp1 > 0; rowp1-- {
row = rowp1 - 1
sum := getDouble(mtxS, row)
for col := rowp1; col < k; col++ {
if bIsTransposed {
sum -= mtxA[row][col] * getDouble(mtxS, col)
} else {
sum -= mtxA[col][row] * getDouble(mtxS, col)
}
}
putDouble(mtxS, row, sum/vecR[row])
}
}
// calcRowQRDecomposition calculates a QR decomposition with Householder
// reflection.
func calcRowQRDecomposition(mtxA [][]float64, vecR []float64, k, n int) bool {
for col := 0; col < k; col++ {
scale := calcColsMaximumNorm(mtxA, col, col, n)
if scale == 0 {
return false
}
for row := col; row < n; row++ {
mtxA[col][row] = mtxA[col][row] / scale
}
euclid := calcRowsEuclideanNorm(mtxA, col, col, n)
factor := 1.0 / euclid / (euclid + math.Abs(mtxA[col][col]))
signum := calcSign(mtxA[col][col])
mtxA[col][col] = mtxA[col][col] + signum*euclid
vecR[col] = -signum * scale * euclid
// apply Householder transformation to A
for c := col + 1; c < k; c++ {
sum := calcRowsSumProduct(mtxA, col, mtxA, c, col, n)
for row := col; row < n; row++ {
mtxA[c][row] = mtxA[c][row] - sum*factor*mtxA[col][row]
}
}
}
return true
}
// calcApplyColsHouseholderTransformation transposed matrices A and Y.
func calcApplyColsHouseholderTransformation(mtxA [][]float64, r int, mtxY [][]float64, n int) {
denominator := calcColsSumProduct(mtxA, r, mtxA, r, r, n)
numerator := calcColsSumProduct(mtxA, r, mtxY, 0, r, n)
factor := 2 * (numerator / denominator)
for col := r; col < n; col++ {
putDouble(mtxY, col, getDouble(mtxY, col)-factor*mtxA[col][r])
}
}
// calcRowMeans calculates means of the rows of matrix.
func calcRowMeans(mtxX, mtxRes [][]float64, c, r int) {
for k := 0; k < r; k++ {
var fSum float64
for i := 0; i < c; i++ {
fSum += mtxX[i][k]
}
mtxRes[k][0] = fSum / float64(c)
}
}
// calcRowsDelta calculates subtract of the rows of matrix.
func calcRowsDelta(mtx, rowMeans [][]float64, c, r int) {
for k := 0; k < r; k++ {
for i := 0; i < c; i++ {
mtx[i][k] = approxSub(mtx[i][k], rowMeans[k][0])
}
}
}
// calcColumnMaximumNorm returns maximum norm of row index R starting in col
// index C; matrix A has count N columns.
func calcColumnMaximumNorm(mtxA [][]float64, r, c, n int) float64 {
var norm float64
for col := c; col < n; col++ {
if norm < math.Abs(mtxA[col][r]) {
norm = math.Abs(mtxA[col][r])
}
}
return norm
}
// calcColsEuclideanNorm returns euclidean norm of row index R starting in
// column index C; matrix A has count N columns.
func calcColsEuclideanNorm(mtxA [][]float64, r, c, n int) float64 {
var norm float64
for col := c; col < n; col++ {
norm += (mtxA[col][r]) * (mtxA[col][r])
}
return math.Sqrt(norm)
}
// calcColsSumProduct returns sum product for given matrix.
func calcColsSumProduct(mtxA [][]float64, a int, mtxB [][]float64, b, c, n int) float64 {
var result float64
for col := c; col < n; col++ {
result += mtxA[col][a] * mtxB[col][b]
}
return result
}
// calcColQRDecomposition same with transposed matrix A, N is count of
// columns, k count of rows.
func calcColQRDecomposition(mtxA [][]float64, vecR []float64, k, n int) bool {
var sum float64
for row := 0; row < k; row++ {
// calculate vector u of the householder transformation
scale := calcColumnMaximumNorm(mtxA, row, row, n)
if scale == 0 {
return false
}
for col := row; col < n; col++ {
mtxA[col][row] = mtxA[col][row] / scale
}
euclid := calcColsEuclideanNorm(mtxA, row, row, n)
factor := 1 / euclid / (euclid + math.Abs(mtxA[row][row]))
signum := calcSign(mtxA[row][row])
mtxA[row][row] = mtxA[row][row] + signum*euclid
vecR[row] = -signum * scale * euclid
// apply Householder transformation to A
for r := row + 1; r < k; r++ {
sum = calcColsSumProduct(mtxA, row, mtxA, r, row, n)
for col := row; col < n; col++ {
mtxA[col][r] = mtxA[col][r] - sum*factor*mtxA[col][row]
}
}
}
return true
}
// calcApplyRowsHouseholderTransformation applies a Householder transformation to a
// column vector Y with is given as Nx1 Matrix. The vector u, from which the
// Householder transformation is built, is the column part in matrix A, with
// column index c, starting with row index c. A is the result of the QR
// decomposition as obtained from calcRowQRDecomposition.
func calcApplyRowsHouseholderTransformation(mtxA [][]float64, c int, mtxY [][]float64, n int) {
denominator := calcRowsSumProduct(mtxA, c, mtxA, c, c, n)
numerator := calcRowsSumProduct(mtxA, c, mtxY, 0, c, n)
factor := 2 * (numerator / denominator)
for row := c; row < n; row++ {
putDouble(mtxY, row, getDouble(mtxY, row)-factor*mtxA[c][row])
}
}
// calcTrendGrowthSimpleRegression calculate simple regression for the calcTrendGrowth.
func calcTrendGrowthSimpleRegression(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, N int) {
var meanX float64
if bConstant {
meanX = calcMeanOverAll(mtxX, N)
for i := 0; i < len(mtxX); i++ {
for j := 0; j < len(mtxX[i]); j++ {
mtxX[i][j] = approxSub(mtxX[i][j], meanX)
}
}
}
sumXY := calcSumProduct(mtxX, mtxY, N)
sumX2 := calcSumProduct(mtxX, mtxX, N)
slope := sumXY / sumX2
var help float64
var intercept float64
if bConstant {
intercept = meanY - slope*meanX
for i := 0; i < len(mtxRes); i++ {
for j := 0; j < len(mtxRes[i]); j++ {
help = newX[i][j]*slope + intercept
if bGrowth {
mtxRes[i][j] = math.Exp(help)
} else {
mtxRes[i][j] = help
}
}
}
} else {
for i := 0; i < len(mtxRes); i++ {
for j := 0; j < len(mtxRes[i]); j++ {
help = newX[i][j] * slope
if bGrowth {
mtxRes[i][j] = math.Exp(help)
} else {
mtxRes[i][j] = help
}
}
}
}
}
// calcTrendGrowthMultipleRegressionPart1 calculate multiple regression for the
// calcTrendGrowth.
func calcTrendGrowthMultipleRegressionPart1(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, RXN, K, N int) {
vecR := make([]float64, N) // for QR decomposition
means := getNewMatrix(K, 1) // mean of each column
slopes := getNewMatrix(1, K) // from b1 to bK
if len(means) == 0 || len(slopes) == 0 {
return
}
if bConstant {
calcColumnMeans(mtxX, means, K, N)
calcColumnsDelta(mtxX, means, K, N)
}
if !calcRowQRDecomposition(mtxX, vecR, K, N) {
return
}
// Later on we will divide by elements of vecR, so make sure that they aren't zero.
bIsSingular := false
for row := 0; row < K && !bIsSingular; row++ {
bIsSingular = bIsSingular || vecR[row] == 0
}
if bIsSingular {
return
}
for col := 0; col < K; col++ {
calcApplyRowsHouseholderTransformation(mtxX, col, mtxY, N)
}
for col := 0; col < K; col++ {
putDouble(slopes, col, getDouble(mtxY, col))
}
calcSolveWithUpperRightTriangle(mtxX, vecR, slopes, K, false)
// Fill result matrix
calcFastMult(newX, slopes, mtxRes, RXN, K, 1)
if bConstant {
intercept := meanY - calcSumProduct(means, slopes, K)
for row := 0; row < RXN; row++ {
mtxRes[0][row] = mtxRes[0][row] + intercept
}
}
if bGrowth {
for i := 0; i < RXN; i++ {
putDouble(mtxRes, i, math.Exp(getDouble(mtxRes, i)))
}
}
}
// calcTrendGrowthMultipleRegressionPart2 calculate multiple regression for the
// calcTrendGrowth.
func calcTrendGrowthMultipleRegressionPart2(bConstant, bGrowth bool, mtxY, mtxX, newX, mtxRes [][]float64, meanY float64, nCXN, K, N int) {
vecR := make([]float64, N) // for QR decomposition
means := getNewMatrix(K, 1) // mean of each row
slopes := getNewMatrix(K, 1) // row from b1 to bK
if len(means) == 0 || len(slopes) == 0 {
return
}
if bConstant {
calcRowMeans(mtxX, means, N, K)
calcRowsDelta(mtxX, means, N, K)
}
if !calcColQRDecomposition(mtxX, vecR, K, N) {
return
}
// later on we will divide by elements of vecR, so make sure that they aren't zero
bIsSingular := false
for row := 0; row < K && !bIsSingular; row++ {
bIsSingular = bIsSingular || vecR[row] == 0
}
if bIsSingular {
return
}
for row := 0; row < K; row++ {
calcApplyColsHouseholderTransformation(mtxX, row, mtxY, N)
}
for col := 0; col < K; col++ {
putDouble(slopes, col, getDouble(mtxY, col))
}
calcSolveWithUpperRightTriangle(mtxX, vecR, slopes, K, true)
// fill result matrix
calcFastMult(slopes, newX, mtxRes, 1, K, nCXN)
if bConstant {
fIntercept := meanY - calcSumProduct(means, slopes, K)
for col := 0; col < nCXN; col++ {
mtxRes[col][0] = mtxRes[col][0] + fIntercept
}
}
if bGrowth {
for i := 0; i < nCXN; i++ {
putDouble(mtxRes, i, math.Exp(getDouble(mtxRes, i)))
}
}
}
// calcTrendGrowthRegression is a part of implementation of the calcTrendGrowth.
func calcTrendGrowthRegression(bConstant, bGrowth bool, trendType, nCXN, nRXN, K, N int, mtxY, mtxX, newX, mtxRes [][]float64) {
if len(mtxRes) == 0 {
return
}
var meanY float64
if bConstant {
copyX, copyY := matrixClone(mtxX), matrixClone(mtxY)
mtxX, mtxY = copyX, copyY
meanY = calcMeanOverAll(mtxY, N)
for i := 0; i < len(mtxY); i++ {
for j := 0; j < len(mtxY[i]); j++ {
mtxY[i][j] = approxSub(mtxY[i][j], meanY)
}
}
}
switch trendType {
case 1:
calcTrendGrowthSimpleRegression(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, N)
case 2:
calcTrendGrowthMultipleRegressionPart1(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, nRXN, K, N)
default:
calcTrendGrowthMultipleRegressionPart2(bConstant, bGrowth, mtxY, mtxX, newX, mtxRes, meanY, nCXN, K, N)
}
}
// calcTrendGrowth returns values along a predicted exponential trend.
func calcTrendGrowth(mtxY, mtxX, newX [][]float64, bConstant, bGrowth bool) ([][]float64, formulaArg) {
getMatrixParams, errArg := prepareTrendGrowth(bGrowth, mtxX, mtxY)
if errArg.Type != ArgEmpty {
return nil, errArg
}
trendType := getMatrixParams.trendType
nCX := getMatrixParams.nCX
nRX := getMatrixParams.nRX
K := getMatrixParams.M
N := getMatrixParams.N
mtxX = getMatrixParams.mtxX
mtxY = getMatrixParams.mtxY
// checking if data samples are enough
if (bConstant && (N < K+1)) || (!bConstant && (N < K)) || (N < 1) || (K < 1) {
return nil, errArg
}
// set the default newX if necessary
nCXN, nRXN := nCX, nRX
if len(newX) == 0 {
newX = matrixClone(mtxX) // mtxX will be changed to X-meanX
} else {
nRXN, nCXN = len(newX[0]), len(newX)
if (trendType == 2 && K != nCXN) || (trendType == 3 && K != nRXN) {
return nil, errArg
}
}
var mtxRes [][]float64
switch trendType {
case 1:
mtxRes = getNewMatrix(nCXN, nRXN)
case 2:
mtxRes = getNewMatrix(1, nRXN)
default:
mtxRes = getNewMatrix(nCXN, 1)
}
calcTrendGrowthRegression(bConstant, bGrowth, trendType, nCXN, nRXN, K, N, mtxY, mtxX, newX, mtxRes)
return mtxRes, errArg
}
// trendGrowth is an implementation of the formula functions GROWTH and TREND.
func (fn *formulaFuncs) trendGrowth(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
if argsList.Len() > 4 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 4 arguments", name))
}
var knowY, knowX, newX [][]float64
var errArg formulaArg
constArg := newBoolFormulaArg(true)
knowY, errArg = newNumberMatrix(argsList.Front().Value.(formulaArg), false)
if errArg.Type == ArgError {
return errArg
}
if argsList.Len() > 1 {
knowX, errArg = newNumberMatrix(argsList.Front().Next().Value.(formulaArg), false)
if errArg.Type == ArgError {
return errArg
}
}
if argsList.Len() > 2 {
newX, errArg = newNumberMatrix(argsList.Front().Next().Next().Value.(formulaArg), false)
if errArg.Type == ArgError {
return errArg
}
}
if argsList.Len() > 3 {
if constArg = argsList.Back().Value.(formulaArg).ToBool(); constArg.Type != ArgNumber {
return constArg
}
}
var mtxNewX [][]float64
for i := 0; i < len(newX); i++ {
for j := 0; j < len(newX[i]); j++ {
for x := len(mtxNewX); x <= j; x++ {
mtxNewX = append(mtxNewX, []float64{})
}
for k := len(mtxNewX[j]); k <= i; k++ {
mtxNewX[j] = append(mtxNewX[j], 0)
}
mtxNewX[j][i] = newX[i][j]
}
}
mtx, errArg := calcTrendGrowth(knowY, knowX, mtxNewX, constArg.Number == 1, name == "GROWTH")
if errArg.Type != ArgEmpty {
return errArg
}
return newMatrixFormulaArg(newFormulaArgMatrix(mtx))
}
// GROWTH function calculates the exponential growth curve through a given set
// of y-values and (optionally), one or more sets of x-values. The function
// then extends the curve to calculate additional y-values for a further
// supplied set of new x-values. The syntax of the function is:
//
// GROWTH(known_y's,[known_x's],[new_x's],[const])
func (fn *formulaFuncs) GROWTH(argsList *list.List) formulaArg {
return fn.trendGrowth("GROWTH", argsList)
}
// HARMEAN function calculates the harmonic mean of a supplied set of values.
// The syntax of the function is:
//
// HARMEAN(number1,[number2],...)
func (fn *formulaFuncs) HARMEAN(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HARMEAN requires at least 1 argument")
}
if minVal := fn.MIN(argsList); minVal.Number < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
number, val, cnt := 0.0, 0.0, 0.0
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
num := arg.ToNumber()
if num.Type != ArgNumber {
continue
}
number = num.Number
case ArgNumber:
number = arg.Number
}
if number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
val += 1 / number
cnt++
}
return newNumberFormulaArg(1 / (val / cnt))
}
// checkHYPGEOMDISTArgs checking arguments for the formula function HYPGEOMDIST
// and HYPGEOM.DIST.
func checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop formulaArg) bool {
return sampleS.Number < 0 ||
sampleS.Number > math.Min(numberSample.Number, populationS.Number) ||
sampleS.Number < math.Max(0, numberSample.Number-numberPop.Number+populationS.Number) ||
numberSample.Number <= 0 ||
numberSample.Number > numberPop.Number ||
populationS.Number <= 0 ||
populationS.Number > numberPop.Number ||
numberPop.Number <= 0
}
// prepareHYPGEOMDISTArgs prepare arguments for the formula function
// HYPGEOMDIST and HYPGEOM.DIST.
func (fn *formulaFuncs) prepareHYPGEOMDISTArgs(name string, argsList *list.List) formulaArg {
if name == "HYPGEOMDIST" && argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOMDIST requires 4 numeric arguments")
}
if name == "HYPGEOM.DIST" && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOM.DIST requires 5 arguments")
}
var sampleS, numberSample, populationS, numberPop, cumulative formulaArg
if sampleS = argsList.Front().Value.(formulaArg).ToNumber(); sampleS.Type != ArgNumber {
return sampleS
}
if numberSample = argsList.Front().Next().Value.(formulaArg).ToNumber(); numberSample.Type != ArgNumber {
return numberSample
}
if populationS = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); populationS.Type != ArgNumber {
return populationS
}
if numberPop = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); numberPop.Type != ArgNumber {
return numberPop
}
if checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if name == "HYPGEOM.DIST" {
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber {
return cumulative
}
}
return newListFormulaArg([]formulaArg{sampleS, numberSample, populationS, numberPop, cumulative})
}
// HYPGEOMdotDIST function returns the value of the hypergeometric distribution
// for a specified number of successes from a population sample. The function
// can calculate the cumulative distribution or the probability density
// function. The syntax of the function is:
//
// HYPGEOM.DIST(sample_s,number_sample,population_s,number_pop,cumulative)
func (fn *formulaFuncs) HYPGEOMdotDIST(argsList *list.List) formulaArg {
args := fn.prepareHYPGEOMDISTArgs("HYPGEOM.DIST", argsList)
if args.Type != ArgList {
return args
}
sampleS, numberSample, populationS, numberPop, cumulative := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4]
if cumulative.Number == 1 {
var res float64
for i := 0; i <= int(sampleS.Number); i++ {
res += binomCoeff(populationS.Number, float64(i)) *
binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-float64(i)) /
binomCoeff(numberPop.Number, numberSample.Number)
}
return newNumberFormulaArg(res)
}
return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) *
binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) /
binomCoeff(numberPop.Number, numberSample.Number))
}
// HYPGEOMDIST function returns the value of the hypergeometric distribution
// for a given number of successes from a sample of a population. The syntax
// of the function is:
//
// HYPGEOMDIST(sample_s,number_sample,population_s,number_pop)
func (fn *formulaFuncs) HYPGEOMDIST(argsList *list.List) formulaArg {
args := fn.prepareHYPGEOMDISTArgs("HYPGEOMDIST", argsList)
if args.Type != ArgList {
return args
}
sampleS, numberSample, populationS, numberPop := args.List[0], args.List[1], args.List[2], args.List[3]
return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) *
binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) /
binomCoeff(numberPop.Number, numberSample.Number))
}
// INTERCEPT function calculates the intercept (the value at the intersection
// of the y axis) of the linear regression line through a supplied set of x-
// and y- values. The syntax of the function is:
//
// INTERCEPT(known_y's,known_x's)
func (fn *formulaFuncs) INTERCEPT(argsList *list.List) formulaArg {
return fn.pearsonProduct("INTERCEPT", 2, argsList)
}
// KURT function calculates the kurtosis of a supplied set of values. The
// syntax of the function is:
//
// KURT(number1,[number2],...)
func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "KURT requires at least 1 argument")
}
mean, stdev := fn.AVERAGE(argsList), fn.STDEV(argsList)
if stdev.Number > 0 {
count, summer := 0.0, 0.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString, ArgNumber:
num := token.ToNumber()
if num.Type == ArgError {
continue
}
summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4)
count++
case ArgList, ArgMatrix:
for _, row := range token.ToList() {
if row.Type == ArgNumber || row.Type == ArgString {
num := row.ToNumber()
if num.Type == ArgError {
continue
}
summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4)
count++
}
}
}
}
if count > 3 {
return newNumberFormulaArg(summer*(count*(count+1)/((count-1)*(count-2)*(count-3))) - (3 * math.Pow(count-1, 2) / ((count - 2) * (count - 3))))
}
}
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
// EXPONdotDIST function returns the value of the exponential distribution for
// a give value of x. The user can specify whether the probability density
// function or the cumulative distribution function is used. The syntax of the
// Expondist function is:
//
// EXPON.DIST(x,lambda,cumulative)
func (fn *formulaFuncs) EXPONdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "EXPON.DIST requires 3 arguments")
}
return fn.EXPONDIST(argsList)
}
// EXPONDIST function returns the value of the exponential distribution for a
// give value of x. The user can specify whether the probability density
// function or the cumulative distribution function is used. The syntax of the
// Expondist function is:
//
// EXPONDIST(x,lambda,cumulative)
func (fn *formulaFuncs) EXPONDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "EXPONDIST requires 3 arguments")
}
var x, lambda, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if lambda = argsList.Front().Next().Value.(formulaArg).ToNumber(); lambda.Type != ArgNumber {
return lambda
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if x.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if lambda.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(1 - math.Exp(-lambda.Number*x.Number))
}
return newNumberFormulaArg(lambda.Number * math.Exp(-lambda.Number*x.Number))
}
// FdotDIST function calculates the Probability Density Function or the
// Cumulative Distribution Function for the F Distribution. This function is
// frequently used to measure the degree of diversity between two data
// sets. The syntax of the function is:
//
// F.DIST(x,deg_freedom1,deg_freedom2,cumulative)
func (fn *formulaFuncs) FdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "F.DIST requires 4 arguments")
}
var x, deg1, deg2, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber {
return deg1
}
if deg2 = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber {
return deg2
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if x.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
maxDeg := math.Pow10(10)
if deg1.Number < 1 || deg1.Number >= maxDeg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if deg2.Number < 1 || deg2.Number >= maxDeg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(1 - getBetaDist(deg2.Number/(deg2.Number+deg1.Number*x.Number), deg2.Number/2, deg1.Number/2))
}
return newNumberFormulaArg(math.Gamma((deg2.Number+deg1.Number)/2) / (math.Gamma(deg1.Number/2) * math.Gamma(deg2.Number/2)) * math.Pow(deg1.Number/deg2.Number, deg1.Number/2) * (math.Pow(x.Number, (deg1.Number-2)/2) / math.Pow(1+(deg1.Number/deg2.Number)*x.Number, (deg1.Number+deg2.Number)/2)))
}
// FDIST function calculates the (right-tailed) F Probability Distribution,
// which measures the degree of diversity between two data sets. The syntax
// of the function is:
//
// FDIST(x,deg_freedom1,deg_freedom2)
func (fn *formulaFuncs) FDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "FDIST requires 3 arguments")
}
var x, deg1, deg2 formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber {
return deg1
}
if deg2 = argsList.Back().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber {
return deg2
}
if x.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
maxDeg := math.Pow10(10)
if deg1.Number < 1 || deg1.Number >= maxDeg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if deg2.Number < 1 || deg2.Number >= maxDeg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New()
args.PushBack(newNumberFormulaArg(deg1.Number * x.Number / (deg1.Number*x.Number + deg2.Number)))
args.PushBack(newNumberFormulaArg(0.5 * deg1.Number))
args.PushBack(newNumberFormulaArg(0.5 * deg2.Number))
args.PushBack(newNumberFormulaArg(0))
args.PushBack(newNumberFormulaArg(1))
return newNumberFormulaArg(1 - fn.BETADIST(args).Number)
}
// FdotDISTdotRT function calculates the (right-tailed) F Probability
// Distribution, which measures the degree of diversity between two data sets.
// The syntax of the function is:
//
// F.DIST.RT(x,deg_freedom1,deg_freedom2)
func (fn *formulaFuncs) FdotDISTdotRT(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "F.DIST.RT requires 3 arguments")
}
return fn.FDIST(argsList)
}
// prepareFinvArgs checking and prepare arguments for the formula functions
// F.INV, F.INV.RT and FINV.
func (fn *formulaFuncs) prepareFinvArgs(name string, argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name))
}
var probability, d1, d2 formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if d1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); d1.Type != ArgNumber {
return d1
}
if d2 = argsList.Back().Value.(formulaArg).ToNumber(); d2.Type != ArgNumber {
return d2
}
if probability.Number <= 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if d1.Number < 1 || d1.Number >= math.Pow10(10) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if d2.Number < 1 || d2.Number >= math.Pow10(10) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newListFormulaArg([]formulaArg{probability, d1, d2})
}
// FdotINV function calculates the inverse of the Cumulative F Distribution
// for a supplied probability. The syntax of the F.Inv function is:
//
// F.INV(probability,deg_freedom1,deg_freedom2)
func (fn *formulaFuncs) FdotINV(argsList *list.List) formulaArg {
args := fn.prepareFinvArgs("F.INV", argsList)
if args.Type != ArgList {
return args
}
probability, d1, d2 := args.List[0], args.List[1], args.List[2]
return newNumberFormulaArg((1/calcBetainv(1-probability.Number, d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number))
}
// FdotINVdotRT function calculates the inverse of the (right-tailed) F
// Probability Distribution for a supplied probability. The syntax of the
// function is:
//
// F.INV.RT(probability,deg_freedom1,deg_freedom2)
func (fn *formulaFuncs) FdotINVdotRT(argsList *list.List) formulaArg {
args := fn.prepareFinvArgs("F.INV.RT", argsList)
if args.Type != ArgList {
return args
}
probability, d1, d2 := args.List[0], args.List[1], args.List[2]
return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number))
}
// FINV function calculates the inverse of the (right-tailed) F Probability
// Distribution for a supplied probability. The syntax of the function is:
//
// FINV(probability,deg_freedom1,deg_freedom2)
func (fn *formulaFuncs) FINV(argsList *list.List) formulaArg {
args := fn.prepareFinvArgs("FINV", argsList)
if args.Type != ArgList {
return args
}
probability, d1, d2 := args.List[0], args.List[1], args.List[2]
return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number))
}
// FdotTEST function returns the F-Test for two supplied arrays. I.e. the
// function returns the two-tailed probability that the variances in the two
// supplied arrays are not significantly different. The syntax of the Ftest
// function is:
//
// F.TEST(array1,array2)
func (fn *formulaFuncs) FdotTEST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "F.TEST requires 2 arguments")
}
array1 := argsList.Front().Value.(formulaArg)
array2 := argsList.Back().Value.(formulaArg)
left, right := array1.ToList(), array2.ToList()
collectMatrix := func(args []formulaArg) (n, accu float64) {
var p, sum float64
for _, arg := range args {
if num := arg.ToNumber(); num.Type == ArgNumber {
x := num.Number - p
y := x / (n + 1)
p += y
accu += n * x * y
n++
sum += num.Number
}
}
return
}
nums, accu := collectMatrix(left)
f3 := nums - 1
if nums == 1 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
f1 := accu / (nums - 1)
if f1 == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
nums, accu = collectMatrix(right)
f4 := nums - 1
if nums == 1 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
f2 := accu / (nums - 1)
if f2 == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
args := list.New()
args.PushBack(newNumberFormulaArg(f1 / f2))
args.PushBack(newNumberFormulaArg(f3))
args.PushBack(newNumberFormulaArg(f4))
probability := (1 - fn.FDIST(args).Number) * 2
if probability > 1 {
probability = 2 - probability
}
return newNumberFormulaArg(probability)
}
// FTEST function returns the F-Test for two supplied arrays. I.e. the function
// returns the two-tailed probability that the variances in the two supplied
// arrays are not significantly different. The syntax of the Ftest function
// is:
//
// FTEST(array1,array2)
func (fn *formulaFuncs) FTEST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "FTEST requires 2 arguments")
}
return fn.FdotTEST(argsList)
}
// LOGINV function calculates the inverse of the Cumulative Log-Normal
// Distribution Function of x, for a supplied probability. The syntax of the
// function is:
//
// LOGINV(probability,mean,standard_dev)
func (fn *formulaFuncs) LOGINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "LOGINV requires 3 arguments")
}
var probability, mean, stdDev formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if probability.Number <= 0 || probability.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if stdDev.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New()
args.PushBack(probability)
args.PushBack(newNumberFormulaArg(0))
args.PushBack(newNumberFormulaArg(1))
norminv := fn.NORMINV(args)
return newNumberFormulaArg(math.Exp(mean.Number + stdDev.Number*norminv.Number))
}
// LOGNORMdotINV function calculates the inverse of the Cumulative Log-Normal
// Distribution Function of x, for a supplied probability. The syntax of the
// function is:
//
// LOGNORM.INV(probability,mean,standard_dev)
func (fn *formulaFuncs) LOGNORMdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.INV requires 3 arguments")
}
return fn.LOGINV(argsList)
}
// LOGNORMdotDIST function calculates the Log-Normal Probability Density
// Function or the Cumulative Log-Normal Distribution Function for a supplied
// value of x. The syntax of the function is:
//
// LOGNORM.DIST(x,mean,standard_dev,cumulative)
func (fn *formulaFuncs) LOGNORMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.DIST requires 4 arguments")
}
var x, mean, stdDev, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if x.Number <= 0 || stdDev.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 1 {
args := list.New()
args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number))
args.PushBack(newNumberFormulaArg(0))
args.PushBack(newNumberFormulaArg(1))
args.PushBack(cumulative)
return fn.NORMDIST(args)
}
return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number * x.Number)) *
math.Exp(0-(math.Pow(math.Log(x.Number)-mean.Number, 2)/(2*math.Pow(stdDev.Number, 2)))))
}
// LOGNORMDIST function calculates the Cumulative Log-Normal Distribution
// Function at a supplied value of x. The syntax of the function is:
//
// LOGNORMDIST(x,mean,standard_dev)
func (fn *formulaFuncs) LOGNORMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "LOGNORMDIST requires 3 arguments")
}
var x, mean, stdDev formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if x.Number <= 0 || stdDev.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New()
args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number))
return fn.NORMSDIST(args)
}
// MODE function returns the statistical mode (the most frequently occurring
// value) of a list of supplied numbers. If there are 2 or more most
// frequently occurring values in the supplied data, the function returns the
// lowest of these values The syntax of the function is:
//
// MODE(number1,[number2],...)
func (fn *formulaFuncs) MODE(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MODE requires at least 1 argument")
}
var values []float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
cells := arg.Value.(formulaArg)
if cells.Type != ArgMatrix && cells.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for _, cell := range cells.ToList() {
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
sort.Float64s(values)
cnt := len(values)
var count, modeCnt int
var mode float64
for i := 0; i < cnt; i++ {
count = 0
for j := 0; j < cnt; j++ {
if j != i && values[j] == values[i] {
count++
}
}
if count > modeCnt {
modeCnt = count
mode = values[i]
}
}
if modeCnt == 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(mode)
}
// MODEdotMULT function returns a vertical array of the statistical modes
// (the most frequently occurring values) within a list of supplied numbers.
// The syntax of the function is:
//
// MODE.MULT(number1,[number2],...)
func (fn *formulaFuncs) MODEdotMULT(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MODE.MULT requires at least 1 argument")
}
var values []float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
cells := arg.Value.(formulaArg)
if cells.Type != ArgMatrix && cells.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for _, cell := range cells.ToList() {
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
sort.Float64s(values)
cnt := len(values)
var count, modeCnt int
var mtx [][]formulaArg
for i := 0; i < cnt; i++ {
count = 0
for j := i + 1; j < cnt; j++ {
if values[i] == values[j] {
count++
}
}
if count > modeCnt {
modeCnt = count
mtx = [][]formulaArg{}
mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])})
} else if count == modeCnt {
mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])})
}
}
if modeCnt == 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newMatrixFormulaArg(mtx)
}
// MODEdotSNGL function returns the statistical mode (the most frequently
// occurring value) within a list of supplied numbers. If there are 2 or more
// most frequently occurring values in the supplied data, the function returns
// the lowest of these values. The syntax of the function is:
//
// MODE.SNGL(number1,[number2],...)
func (fn *formulaFuncs) MODEdotSNGL(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MODE.SNGL requires at least 1 argument")
}
return fn.MODE(argsList)
}
// NEGBINOMdotDIST function calculates the probability mass function or the
// cumulative distribution function for the Negative Binomial Distribution.
// This gives the probability that there will be a given number of failures
// before a required number of successes is achieved. The syntax of the
// function is:
//
// NEGBINOM.DIST(number_f,number_s,probability_s,cumulative)
func (fn *formulaFuncs) NEGBINOMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOM.DIST requires 4 arguments")
}
var f, s, probability, cumulative formulaArg
if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber {
return f
}
if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber {
return s
}
if probability = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber {
return cumulative
}
if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(1 - getBetaDist(1-probability.Number, f.Number+1, s.Number))
}
return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number))
}
// NEGBINOMDIST function calculates the Negative Binomial Distribution for a
// given set of parameters. This gives the probability that there will be a
// specified number of failures before a required number of successes is
// achieved. The syntax of the function is:
//
// NEGBINOMDIST(number_f,number_s,probability_s)
func (fn *formulaFuncs) NEGBINOMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOMDIST requires 3 arguments")
}
var f, s, probability formulaArg
if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber {
return f
}
if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber {
return s
}
if probability = argsList.Back().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number))
}
// NORMdotDIST function calculates the Normal Probability Density Function or
// the Cumulative Normal Distribution. Function for a supplied set of
// parameters. The syntax of the function is:
//
// NORM.DIST(x,mean,standard_dev,cumulative)
func (fn *formulaFuncs) NORMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.DIST requires 4 arguments")
}
return fn.NORMDIST(argsList)
}
// NORMDIST function calculates the Normal Probability Density Function or the
// Cumulative Normal Distribution. Function for a supplied set of parameters.
// The syntax of the function is:
//
// NORMDIST(x,mean,standard_dev,cumulative)
func (fn *formulaFuncs) NORMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMDIST requires 4 arguments")
}
var x, mean, stdDev, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if stdDev.Number < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(0.5 * (1 + math.Erf((x.Number-mean.Number)/(stdDev.Number*math.Sqrt(2)))))
}
return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number)) * math.Exp(0-(math.Pow(x.Number-mean.Number, 2)/(2*(stdDev.Number*stdDev.Number)))))
}
// NORMdotINV function calculates the inverse of the Cumulative Normal
// Distribution Function for a supplied value of x, and a supplied
// distribution mean & standard deviation. The syntax of the function is:
//
// NORM.INV(probability,mean,standard_dev)
func (fn *formulaFuncs) NORMdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.INV requires 3 arguments")
}
return fn.NORMINV(argsList)
}
// NORMINV function calculates the inverse of the Cumulative Normal
// Distribution Function for a supplied value of x, and a supplied
// distribution mean & standard deviation. The syntax of the function is:
//
// NORMINV(probability,mean,standard_dev)
func (fn *formulaFuncs) NORMINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMINV requires 3 arguments")
}
var prob, mean, stdDev formulaArg
if prob = argsList.Front().Value.(formulaArg).ToNumber(); prob.Type != ArgNumber {
return prob
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if prob.Number < 0 || prob.Number > 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if stdDev.Number < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
inv, err := norminv(prob.Number)
if err != nil {
return newErrorFormulaArg(err.Error(), err.Error())
}
return newNumberFormulaArg(inv*stdDev.Number + mean.Number)
}
// NORMdotSdotDIST function calculates the Standard Normal Cumulative
// Distribution Function for a supplied value. The syntax of the function
// is:
//
// NORM.S.DIST(z)
func (fn *formulaFuncs) NORMdotSdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.DIST requires 2 numeric arguments")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
args.PushBack(argsList.Back().Value.(formulaArg))
return fn.NORMDIST(args)
}
// NORMSDIST function calculates the Standard Normal Cumulative Distribution
// Function for a supplied value. The syntax of the function is:
//
// NORMSDIST(z)
func (fn *formulaFuncs) NORMSDIST(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMSDIST requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1, Boolean: true})
return fn.NORMDIST(args)
}
// NORMSINV function calculates the inverse of the Standard Normal Cumulative
// Distribution Function for a supplied probability value. The syntax of the
// function is:
//
// NORMSINV(probability)
func (fn *formulaFuncs) NORMSINV(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMSINV requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
return fn.NORMINV(args)
}
// NORMdotSdotINV function calculates the inverse of the Standard Normal
// Cumulative Distribution Function for a supplied probability value. The
// syntax of the function is:
//
// NORM.S.INV(probability)
func (fn *formulaFuncs) NORMdotSdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.INV requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
return fn.NORMINV(args)
}
// norminv returns the inverse of the normal cumulative distribution for the
// specified value.
func norminv(p float64) (float64, error) {
a := map[int]float64{
1: -3.969683028665376e+01, 2: 2.209460984245205e+02, 3: -2.759285104469687e+02,
4: 1.383577518672690e+02, 5: -3.066479806614716e+01, 6: 2.506628277459239e+00,
}
b := map[int]float64{
1: -5.447609879822406e+01, 2: 1.615858368580409e+02, 3: -1.556989798598866e+02,
4: 6.680131188771972e+01, 5: -1.328068155288572e+01,
}
c := map[int]float64{
1: -7.784894002430293e-03, 2: -3.223964580411365e-01, 3: -2.400758277161838e+00,
4: -2.549732539343734e+00, 5: 4.374664141464968e+00, 6: 2.938163982698783e+00,
}
d := map[int]float64{
1: 7.784695709041462e-03, 2: 3.224671290700398e-01, 3: 2.445134137142996e+00,
4: 3.754408661907416e+00,
}
pLow := 0.02425 // Use lower region approx. below this
pHigh := 1 - pLow // Use upper region approx. above this
if 0 < p && p < pLow {
// Rational approximation for lower region.
q := math.Sqrt(-2 * math.Log(p))
return (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
} else if pLow <= p && p <= pHigh {
// Rational approximation for central region.
q := p - 0.5
r := q * q
f1 := ((((a[1]*r+a[2])*r+a[3])*r+a[4])*r + a[5]) * r
f2 := (b[1]*r + b[2]) * r
f3 := ((math.Nextafter(f2, f2)+b[3])*r + b[4]) * r
f4 := (math.Nextafter(f3, f3) + b[5]) * r
return (math.Nextafter(f1, f1) + a[6]) * q /
(math.Nextafter(f4, f4) + 1), nil
} else if pHigh < p && p < 1 {
// Rational approximation for upper region.
q := math.Sqrt(-2 * math.Log(1-p))
return -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
}
return 0, errors.New(formulaErrorNUM)
}
// kth is an implementation of the formula functions LARGE and SMALL.
func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
}
array := argsList.Front().Value.(formulaArg).ToList()
argK := argsList.Back().Value.(formulaArg).ToNumber()
if argK.Type != ArgNumber {
return argK
}
k := int(argK.Number)
if k < 1 {
return newErrorFormulaArg(formulaErrorNUM, "k should be > 0")
}
var data []float64
for _, arg := range array {
if arg.Type == ArgNumber {
data = append(data, arg.Number)
}
}
if len(data) < k {
return newErrorFormulaArg(formulaErrorNUM, "k should be <= length of array")
}
sort.Float64s(data)
if name == "LARGE" {
return newNumberFormulaArg(data[len(data)-k])
}
return newNumberFormulaArg(data[k-1])
}
// LARGE function returns the k'th largest value from an array of numeric
// values. The syntax of the function is:
//
// LARGE(array,k)
func (fn *formulaFuncs) LARGE(argsList *list.List) formulaArg {
return fn.kth("LARGE", argsList)
}
// MAX function returns the largest value from a supplied set of numeric
// values. The syntax of the function is:
//
// MAX(number1,[number2],...)
func (fn *formulaFuncs) MAX(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MAX requires at least 1 argument")
}
return fn.maxValue(false, argsList)
}
// MAXA function returns the largest value from a supplied set of numeric
// values, while counting text and the logical value FALSE as the value 0 and
// counting the logical value TRUE as the value 1. The syntax of the function
// is:
//
// MAXA(number1,[number2],...)
func (fn *formulaFuncs) MAXA(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MAXA requires at least 1 argument")
}
return fn.maxValue(true, argsList)
}
// MAXIFS function returns the maximum value from a subset of values that are
// specified according to one or more criteria. The syntax of the function
// is:
//
// MAXIFS(max_range,criteria_range1,criteria1,[criteria_range2,criteria2],...)
func (fn *formulaFuncs) MAXIFS(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "MAXIFS requires at least 3 arguments")
}
if argsList.Len()%2 != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var args []formulaArg
maxVal, maxRange := -math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
for _, ref := range formulaIfsMatch(args) {
if num := maxRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && maxVal < num.Number {
maxVal = num.Number
}
}
if maxVal == -math.MaxFloat64 {
maxVal = 0
}
return newNumberFormulaArg(maxVal)
}
// calcListMatrixMax is part of the implementation max.
func calcListMatrixMax(maxa bool, maxVal float64, arg formulaArg) float64 {
for _, cell := range arg.ToList() {
if cell.Type == ArgNumber && cell.Number > maxVal {
if maxa && cell.Boolean || !cell.Boolean {
maxVal = cell.Number
}
}
}
return maxVal
}
// maxValue is an implementation of the formula functions MAX and MAXA.
func (fn *formulaFuncs) maxValue(maxa bool, argsList *list.List) formulaArg {
maxVal := -math.MaxFloat64
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
if !maxa && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
continue
} else {
num := arg.ToBool()
if num.Type == ArgNumber && num.Number > maxVal {
maxVal = num.Number
continue
}
}
num := arg.ToNumber()
if num.Type != ArgError && num.Number > maxVal {
maxVal = num.Number
}
case ArgNumber:
if arg.Number > maxVal {
maxVal = arg.Number
}
case ArgList, ArgMatrix:
maxVal = calcListMatrixMax(maxa, maxVal, arg)
case ArgError:
return arg
}
}
if maxVal == -math.MaxFloat64 {
maxVal = 0
}
return newNumberFormulaArg(maxVal)
}
// MEDIAN function returns the statistical median (the middle value) of a list
// of supplied numbers. The syntax of the function is:
//
// MEDIAN(number1,[number2],...)
func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument")
}
var values []float64
var median float64
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
value := arg.ToNumber()
if value.Type != ArgNumber {
return value
}
values = append(values, value.Number)
case ArgNumber:
values = append(values, arg.Number)
case ArgMatrix:
for _, row := range arg.Matrix {
for _, cell := range row {
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
}
}
if len(values) == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
sort.Float64s(values)
if len(values)%2 == 0 {
median = (values[len(values)/2-1] + values[len(values)/2]) / 2
} else {
median = values[len(values)/2]
}
return newNumberFormulaArg(median)
}
// MIN function returns the smallest value from a supplied set of numeric
// values. The syntax of the function is:
//
// MIN(number1,[number2],...)
func (fn *formulaFuncs) MIN(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MIN requires at least 1 argument")
}
return fn.minValue(false, argsList)
}
// MINA function returns the smallest value from a supplied set of numeric
// values, while counting text and the logical value FALSE as the value 0 and
// counting the logical value TRUE as the value 1. The syntax of the function
// is:
//
// MINA(number1,[number2],...)
func (fn *formulaFuncs) MINA(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MINA requires at least 1 argument")
}
return fn.minValue(true, argsList)
}
// MINIFS function returns the minimum value from a subset of values that are
// specified according to one or more criteria. The syntax of the function
// is:
//
// MINIFS(min_range,criteria_range1,criteria1,[criteria_range2,criteria2],...)
func (fn *formulaFuncs) MINIFS(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "MINIFS requires at least 3 arguments")
}
if argsList.Len()%2 != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var args []formulaArg
minVal, minRange := math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
args = append(args, arg.Value.(formulaArg))
}
for _, ref := range formulaIfsMatch(args) {
if num := minRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && minVal > num.Number {
minVal = num.Number
}
}
if minVal == math.MaxFloat64 {
minVal = 0
}
return newNumberFormulaArg(minVal)
}
// calcListMatrixMin is part of the implementation min.
func calcListMatrixMin(mina bool, minVal float64, arg formulaArg) float64 {
for _, cell := range arg.ToList() {
if cell.Type == ArgNumber && cell.Number < minVal {
if mina && cell.Boolean || !cell.Boolean {
minVal = cell.Number
}
}
}
return minVal
}
// minValue is an implementation of the formula functions MIN and MINA.
func (fn *formulaFuncs) minValue(mina bool, argsList *list.List) formulaArg {
minVal := math.MaxFloat64
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
if !mina && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
continue
} else {
num := arg.ToBool()
if num.Type == ArgNumber && num.Number < minVal {
minVal = num.Number
continue
}
}
num := arg.ToNumber()
if num.Type != ArgError && num.Number < minVal {
minVal = num.Number
}
case ArgNumber:
if arg.Number < minVal {
minVal = arg.Number
}
case ArgList, ArgMatrix:
minVal = calcListMatrixMin(mina, minVal, arg)
case ArgError:
return arg
}
}
if minVal == math.MaxFloat64 {
minVal = 0
}
return newNumberFormulaArg(minVal)
}
// pearsonProduct is an implementation of the formula functions FORECAST,
// FORECAST.LINEAR, INTERCEPT, PEARSON, RSQ and SLOPE.
func (fn *formulaFuncs) pearsonProduct(name string, n int, argsList *list.List) formulaArg {
if argsList.Len() != n {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires %d arguments", name, n))
}
var fx formulaArg
array1 := argsList.Back().Value.(formulaArg).ToList()
array2 := argsList.Front().Value.(formulaArg).ToList()
if name == "PEARSON" || name == "RSQ" {
array1, array2 = array2, array1
}
if n == 3 {
if fx = argsList.Front().Value.(formulaArg).ToNumber(); fx.Type != ArgNumber {
return fx
}
array2 = argsList.Front().Next().Value.(formulaArg).ToList()
}
if len(array1) != len(array2) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var sum, deltaX, deltaY, x, y, length float64
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i], array2[i]
if num1.Type != ArgNumber || num2.Type != ArgNumber {
continue
}
x += num1.Number
y += num2.Number
length++
}
x /= length
y /= length
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i], array2[i]
if num1.Type != ArgNumber || num2.Type != ArgNumber {
continue
}
sum += (num1.Number - x) * (num2.Number - y)
deltaX += (num1.Number - x) * (num1.Number - x)
deltaY += (num2.Number - y) * (num2.Number - y)
}
if sum*deltaX*deltaY == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(map[string]float64{
"FORECAST": y + sum/deltaX*(fx.Number-x),
"FORECAST.LINEAR": y + sum/deltaX*(fx.Number-x),
"INTERCEPT": y - sum/deltaX*x,
"PEARSON": sum / math.Sqrt(deltaX*deltaY),
"RSQ": math.Pow(sum/math.Sqrt(deltaX*deltaY), 2),
"SLOPE": sum / deltaX,
}[name])
}
// PEARSON function calculates the Pearson Product-Moment Correlation
// Coefficient for two sets of values. The syntax of the function is:
//
// PEARSON(array1,array2)
func (fn *formulaFuncs) PEARSON(argsList *list.List) formulaArg {
return fn.pearsonProduct("PEARSON", 2, argsList)
}
// PERCENTILEdotEXC function returns the k'th percentile (i.e. the value below
// which k% of the data values fall) for a supplied range of values and a
// supplied k (between 0 & 1 exclusive).The syntax of the function is:
//
// PERCENTILE.EXC(array,k)
func (fn *formulaFuncs) PERCENTILEdotEXC(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.EXC requires 2 arguments")
}
array := argsList.Front().Value.(formulaArg).ToList()
k := argsList.Back().Value.(formulaArg).ToNumber()
if k.Type != ArgNumber {
return k
}
if k.Number <= 0 || k.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
var numbers []float64
for _, arg := range array {
if arg.Type == ArgError {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
sort.Float64s(numbers)
idx := k.Number * (float64(cnt) + 1)
base := math.Floor(idx)
next := base - 1
proportion := math.Nextafter(idx, idx) - base
return newNumberFormulaArg(numbers[int(next)] + ((numbers[int(base)] - numbers[int(next)]) * proportion))
}
// PERCENTILEdotINC function returns the k'th percentile (i.e. the value below
// which k% of the data values fall) for a supplied range of values and a
// supplied k. The syntax of the function is:
//
// PERCENTILE.INC(array,k)
func (fn *formulaFuncs) PERCENTILEdotINC(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.INC requires 2 arguments")
}
return fn.PERCENTILE(argsList)
}
// PERCENTILE function returns the k'th percentile (i.e. the value below which
// k% of the data values fall) for a supplied range of values and a supplied
// k. The syntax of the function is:
//
// PERCENTILE(array,k)
func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE requires 2 arguments")
}
array := argsList.Front().Value.(formulaArg).ToList()
k := argsList.Back().Value.(formulaArg).ToNumber()
if k.Type != ArgNumber {
return k
}
if k.Number < 0 || k.Number > 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var numbers []float64
for _, arg := range array {
if arg.Type == ArgError {
return arg
}
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
sort.Float64s(numbers)
idx := k.Number * (float64(cnt) - 1)
base := math.Floor(idx)
if idx == base {
return newNumberFormulaArg(numbers[int(idx)])
}
next := base + 1
proportion := math.Nextafter(idx, idx) - base
return newNumberFormulaArg(numbers[int(base)] + ((numbers[int(next)] - numbers[int(base)]) * proportion))
}
// percentrank is an implementation of the formula functions PERCENTRANK and
// PERCENTRANK.INC.
func (fn *formulaFuncs) percentrank(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 && argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 or 3 arguments", name))
}
array := argsList.Front().Value.(formulaArg).ToList()
x := argsList.Front().Next().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
var numbers []float64
for _, arg := range array {
if arg.Type == ArgError {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
sort.Float64s(numbers)
if x.Number < numbers[0] || x.Number > numbers[cnt-1] {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
pos, significance := float64(inFloat64Slice(numbers, x.Number)), newNumberFormulaArg(3)
if argsList.Len() == 3 {
if significance = argsList.Back().Value.(formulaArg).ToNumber(); significance.Type != ArgNumber {
return significance
}
if significance.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s arguments significance should be > 1", name))
}
}
if pos == -1 {
pos = 0
cmp := numbers[0]
for cmp < x.Number {
pos++
cmp = numbers[int(pos)]
}
pos--
pos += (x.Number - numbers[int(pos)]) / (cmp - numbers[int(pos)])
}
pow := math.Pow(10, significance.Number)
digit := pow * pos / (float64(cnt) - 1)
if name == "PERCENTRANK.EXC" {
digit = pow * (pos + 1) / (float64(cnt) + 1)
}
return newNumberFormulaArg(math.Floor(digit) / pow)
}
// PERCENTRANKdotEXC function calculates the relative position, between 0 and
// 1 (exclusive), of a specified value within a supplied array. The syntax of
// the function is:
//
// PERCENTRANK.EXC(array,x,[significance])
func (fn *formulaFuncs) PERCENTRANKdotEXC(argsList *list.List) formulaArg {
return fn.percentrank("PERCENTRANK.EXC", argsList)
}
// PERCENTRANKdotINC function calculates the relative position, between 0 and
// 1 (inclusive), of a specified value within a supplied array.The syntax of
// the function is:
//
// PERCENTRANK.INC(array,x,[significance])
func (fn *formulaFuncs) PERCENTRANKdotINC(argsList *list.List) formulaArg {
return fn.percentrank("PERCENTRANK.INC", argsList)
}
// PERCENTRANK function calculates the relative position of a specified value,
// within a set of values, as a percentage. The syntax of the function is:
//
// PERCENTRANK(array,x,[significance])
func (fn *formulaFuncs) PERCENTRANK(argsList *list.List) formulaArg {
return fn.percentrank("PERCENTRANK", argsList)
}
// PERMUT function calculates the number of permutations of a specified number
// of objects from a set of objects. The syntax of the function is:
//
// PERMUT(number,number_chosen)
func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "PERMUT requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
chosen := argsList.Back().Value.(formulaArg).ToNumber()
if number.Type != ArgNumber {
return number
}
if chosen.Type != ArgNumber {
return chosen
}
if number.Number < chosen.Number {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number)))
}
// PERMUTATIONA function calculates the number of permutations, with
// repetitions, of a specified number of objects from a set. The syntax of
// the function is:
//
// PERMUTATIONA(number,number_chosen)
func (fn *formulaFuncs) PERMUTATIONA(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "PERMUTATIONA requires 2 numeric arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
chosen := argsList.Back().Value.(formulaArg).ToNumber()
if number.Type != ArgNumber {
return number
}
if chosen.Type != ArgNumber {
return chosen
}
num, numChosen := math.Floor(number.Number), math.Floor(chosen.Number)
if num < 0 || numChosen < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(math.Pow(num, numChosen))
}
// PHI function returns the value of the density function for a standard normal
// distribution for a supplied number. The syntax of the function is:
//
// PHI(x)
func (fn *formulaFuncs) PHI(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "PHI requires 1 argument")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
return newNumberFormulaArg(0.39894228040143268 * math.Exp(-(x.Number*x.Number)/2))
}
// QUARTILE function returns a requested quartile of a supplied range of
// values. The syntax of the function is:
//
// QUARTILE(array,quart)
func (fn *formulaFuncs) QUARTILE(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE requires 2 arguments")
}
quart := argsList.Back().Value.(formulaArg).ToNumber()
if quart.Type != ArgNumber {
return quart
}
if quart.Number < 0 || quart.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(newNumberFormulaArg(quart.Number / 4))
return fn.PERCENTILE(args)
}
// QUARTILEdotEXC function returns a requested quartile of a supplied range of
// values, based on a percentile range of 0 to 1 exclusive. The syntax of the
// function is:
//
// QUARTILE.EXC(array,quart)
func (fn *formulaFuncs) QUARTILEdotEXC(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.EXC requires 2 arguments")
}
quart := argsList.Back().Value.(formulaArg).ToNumber()
if quart.Type != ArgNumber {
return quart
}
if quart.Number <= 0 || quart.Number >= 4 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(newNumberFormulaArg(quart.Number / 4))
return fn.PERCENTILEdotEXC(args)
}
// QUARTILEdotINC function returns a requested quartile of a supplied range of
// values. The syntax of the function is:
//
// QUARTILE.INC(array,quart)
func (fn *formulaFuncs) QUARTILEdotINC(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.INC requires 2 arguments")
}
return fn.QUARTILE(argsList)
}
// rank is an implementation of the formula functions RANK and RANK.EQ.
func (fn *formulaFuncs) rank(name string, argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 3 arguments", name))
}
num := argsList.Front().Value.(formulaArg).ToNumber()
if num.Type != ArgNumber {
return num
}
var arr []float64
for _, arg := range argsList.Front().Next().Value.(formulaArg).ToList() {
if arg.Type == ArgNumber {
arr = append(arr, arg.Number)
}
}
sort.Float64s(arr)
order := newNumberFormulaArg(0)
if argsList.Len() == 3 {
if order = argsList.Back().Value.(formulaArg).ToNumber(); order.Type != ArgNumber {
return order
}
}
if order.Number == 0 {
sort.Sort(sort.Reverse(sort.Float64Slice(arr)))
}
if idx := inFloat64Slice(arr, num.Number); idx != -1 {
return newNumberFormulaArg(float64(idx + 1))
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// RANKdotEQ function returns the statistical rank of a given value, within a
// supplied array of values. If there are duplicate values in the list, these
// are given the same rank. The syntax of the function is:
//
// RANK.EQ(number,ref,[order])
func (fn *formulaFuncs) RANKdotEQ(argsList *list.List) formulaArg {
return fn.rank("RANK.EQ", argsList)
}
// RANK function returns the statistical rank of a given value, within a
// supplied array of values. If there are duplicate values in the list, these
// are given the same rank. The syntax of the function is:
//
// RANK(number,ref,[order])
func (fn *formulaFuncs) RANK(argsList *list.List) formulaArg {
return fn.rank("RANK", argsList)
}
// RSQ function calculates the square of the Pearson Product-Moment Correlation
// Coefficient for two supplied sets of values. The syntax of the function
// is:
//
// RSQ(known_y's,known_x's)
func (fn *formulaFuncs) RSQ(argsList *list.List) formulaArg {
return fn.pearsonProduct("RSQ", 2, argsList)
}
// skew is an implementation of the formula functions SKEW and SKEW.P.
func (fn *formulaFuncs) skew(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
mean := fn.AVERAGE(argsList)
var stdDev formulaArg
var count, summer float64
if name == "SKEW" {
stdDev = fn.STDEV(argsList)
} else {
stdDev = fn.STDEVP(argsList)
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgNumber, ArgString:
num := token.ToNumber()
if num.Type == ArgError {
return num
}
summer += math.Pow((num.Number-mean.Number)/stdDev.Number, 3)
count++
case ArgList, ArgMatrix:
for _, cell := range token.ToList() {
if cell.Type != ArgNumber {
continue
}
summer += math.Pow((cell.Number-mean.Number)/stdDev.Number, 3)
count++
}
}
}
if count > 2 {
if name == "SKEW" {
return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2))))
}
return newNumberFormulaArg(summer / count)
}
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
// SKEW function calculates the skewness of the distribution of a supplied set
// of values. The syntax of the function is:
//
// SKEW(number1,[number2],...)
func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg {
return fn.skew("SKEW", argsList)
}
// SKEWdotP function calculates the skewness of the distribution of a supplied
// set of values. The syntax of the function is:
//
// SKEW.P(number1,[number2],...)
func (fn *formulaFuncs) SKEWdotP(argsList *list.List) formulaArg {
return fn.skew("SKEW.P", argsList)
}
// SLOPE returns the slope of the linear regression line through data points in
// known_y's and known_x's. The slope is the vertical distance divided by the
// horizontal distance between any two points on the line, which is the rate
// of change along the regression line. The syntax of the function is:
//
// SLOPE(known_y's,known_x's)
func (fn *formulaFuncs) SLOPE(argsList *list.List) formulaArg {
return fn.pearsonProduct("SLOPE", 2, argsList)
}
// SMALL function returns the k'th smallest value from an array of numeric
// values. The syntax of the function is:
//
// SMALL(array,k)
func (fn *formulaFuncs) SMALL(argsList *list.List) formulaArg {
return fn.kth("SMALL", argsList)
}
// STANDARDIZE function returns a normalized value of a distribution that is
// characterized by a supplied mean and standard deviation. The syntax of the
// function is:
//
// STANDARDIZE(x,mean,standard_dev)
func (fn *formulaFuncs) STANDARDIZE(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "STANDARDIZE requires 3 arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
mean := argsList.Front().Next().Value.(formulaArg).ToNumber()
if mean.Type != ArgNumber {
return mean
}
stdDev := argsList.Back().Value.(formulaArg).ToNumber()
if stdDev.Type != ArgNumber {
return stdDev
}
if stdDev.Number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg((x.Number - mean.Number) / stdDev.Number)
}
// stdevp is an implementation of the formula functions STDEVP, STDEV.P and
// STDEVPA.
func (fn *formulaFuncs) stdevp(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
fnName := "VARP"
if name == "STDEVPA" {
fnName = "VARPA"
}
varp := fn.vars(fnName, argsList)
if varp.Type != ArgNumber {
return varp
}
return newNumberFormulaArg(math.Sqrt(varp.Number))
}
// STDEVP function calculates the standard deviation of a supplied set of
// values. The syntax of the function is:
//
// STDEVP(number1,[number2],...)
func (fn *formulaFuncs) STDEVP(argsList *list.List) formulaArg {
return fn.stdevp("STDEVP", argsList)
}
// STDEVdotP function calculates the standard deviation of a supplied set of
// values.
//
// STDEV.P( number1, [number2], ... )
func (fn *formulaFuncs) STDEVdotP(argsList *list.List) formulaArg {
return fn.stdevp("STDEV.P", argsList)
}
// STDEVPA function calculates the standard deviation of a supplied set of
// values. The syntax of the function is:
//
// STDEVPA(number1,[number2],...)
func (fn *formulaFuncs) STDEVPA(argsList *list.List) formulaArg {
return fn.stdevp("STDEVPA", argsList)
}
// STEYX function calculates the standard error for the line of best fit,
// through a supplied set of x- and y- values. The syntax of the function is:
//
// STEYX(known_y's,known_x's)
func (fn *formulaFuncs) STEYX(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "STEYX requires 2 arguments")
}
array1 := argsList.Back().Value.(formulaArg).ToList()
array2 := argsList.Front().Value.(formulaArg).ToList()
if len(array1) != len(array2) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var count, sumX, sumY, squareX, squareY, sigmaXY float64
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i], array2[i]
if num1.Type != ArgNumber || num2.Type != ArgNumber {
continue
}
sumX += num1.Number
sumY += num2.Number
squareX += num1.Number * num1.Number
squareY += num2.Number * num2.Number
sigmaXY += num1.Number * num2.Number
count++
}
if count < 3 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
dx, dy := sumX/count, sumY/count
sigma1 := squareY - 2*dy*sumY + count*dy*dy
sigma2 := sigmaXY - dy*sumX - sumY*dx + count*dy*dx
sigma3 := squareX - 2*dx*sumX + count*dx*dx
return newNumberFormulaArg(math.Sqrt((sigma1 - (sigma2*sigma2)/sigma3) / (count - 2)))
}
// getTDist is an implementation for the beta distribution probability density
// function.
func getTDist(T, fDF, nType float64) float64 {
var res float64
switch nType {
case 1:
res = 0.5 * getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5)
case 2:
res = getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5)
case 3:
res = math.Pow(1+(T*T/fDF), -(fDF+1)/2) / (math.Sqrt(fDF) * getBeta(0.5, fDF/2.0))
case 4:
X := fDF / (T*T + fDF)
R := 0.5 * getBetaDist(X, 0.5*fDF, 0.5)
res = 1 - R
if T < 0 {
res = R
}
}
return res
}
// TdotDIST function calculates the one-tailed Student's T Distribution, which
// is a continuous probability distribution that is frequently used for
// testing hypotheses on small sample data sets. The syntax of the function
// is:
//
// T.DIST(x,degrees_freedom,cumulative)
func (fn *formulaFuncs) TdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "T.DIST requires 3 arguments")
}
var x, degrees, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber {
return cumulative
}
if cumulative.Number == 1 && degrees.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative.Number == 0 {
if degrees.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if degrees.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 3))
}
return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 4))
}
// TdotDISTdot2T function calculates the two-tailed Student's T Distribution,
// which is a continuous probability distribution that is frequently used for
// testing hypotheses on small sample data sets. The syntax of the function
// is:
//
// T.DIST.2T(x,degrees_freedom)
func (fn *formulaFuncs) TdotDISTdot2T(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.2T requires 2 arguments")
}
var x, degrees formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if x.Number < 0 || degrees.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 2))
}
// TdotDISTdotRT function calculates the right-tailed Student's T Distribution,
// which is a continuous probability distribution that is frequently used for
// testing hypotheses on small sample data sets. The syntax of the function
// is:
//
// T.DIST.RT(x,degrees_freedom)
func (fn *formulaFuncs) TdotDISTdotRT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.RT requires 2 arguments")
}
var x, degrees formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if degrees.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
v := getTDist(x.Number, degrees.Number, 1)
if x.Number < 0 {
v = 1 - v
}
return newNumberFormulaArg(v)
}
// TDIST function calculates the Student's T Distribution, which is a
// continuous probability distribution that is frequently used for testing
// hypotheses on small sample data sets. The syntax of the function is:
//
// TDIST(x,degrees_freedom,tails)
func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TDIST requires 3 arguments")
}
var x, degrees, tails formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if tails = argsList.Back().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber {
return tails
}
if x.Number < 0 || degrees.Number < 1 || (tails.Number != 1 && tails.Number != 2) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(getTDist(x.Number, degrees.Number, tails.Number))
}
// TdotINV function calculates the left-tailed inverse of the Student's T
// Distribution, which is a continuous probability distribution that is
// frequently used for testing hypotheses on small sample data sets. The
// syntax of the function is:
//
// T.INV(probability,degrees_freedom)
func (fn *formulaFuncs) TdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "T.INV requires 2 arguments")
}
var probability, degrees formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if probability.Number <= 0 || probability.Number >= 1 || degrees.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if probability.Number < 0.5 {
return newNumberFormulaArg(-calcIterateInverse(calcInverseIterator{
name: "T.INV",
fp: 1 - probability.Number,
fDF: degrees.Number,
nT: 4,
}, degrees.Number/2, degrees.Number))
}
return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{
name: "T.INV",
fp: probability.Number,
fDF: degrees.Number,
nT: 4,
}, degrees.Number/2, degrees.Number))
}
// TdotINVdot2T function calculates the inverse of the two-tailed Student's T
// Distribution, which is a continuous probability distribution that is
// frequently used for testing hypotheses on small sample data sets. The
// syntax of the function is:
//
// T.INV.2T(probability,degrees_freedom)
func (fn *formulaFuncs) TdotINVdot2T(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "T.INV.2T requires 2 arguments")
}
var probability, degrees formulaArg
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
return degrees
}
if probability.Number <= 0 || probability.Number > 1 || degrees.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{
name: "T.INV.2T",
fp: probability.Number,
fDF: degrees.Number,
nT: 2,
}, degrees.Number/2, degrees.Number))
}
// TINV function calculates the inverse of the two-tailed Student's T
// Distribution, which is a continuous probability distribution that is
// frequently used for testing hypotheses on small sample data sets. The
// syntax of the function is:
//
// TINV(probability,degrees_freedom)
func (fn *formulaFuncs) TINV(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "TINV requires 2 arguments")
}
return fn.TdotINVdot2T(argsList)
}
// TREND function calculates the linear trend line through a given set of
// y-values and (optionally), a given set of x-values. The function then
// extends the linear trendline to calculate additional y-values for a further
// supplied set of new x-values. The syntax of the function is:
//
// TREND(known_y's,[known_x's],[new_x's],[const])
func (fn *formulaFuncs) TREND(argsList *list.List) formulaArg {
return fn.trendGrowth("TREND", argsList)
}
// tTest calculates the probability associated with the Student's T Test.
func tTest(bTemplin bool, mtx1, mtx2 [][]formulaArg, c1, c2, r1, r2 int) (float64, float64, bool) {
var cnt1, cnt2, sum1, sumSqr1, sum2, sumSqr2 float64
var fVal formulaArg
for i := 0; i < c1; i++ {
for j := 0; j < r1; j++ {
if fVal = mtx1[i][j]; fVal.Type == ArgNumber {
sum1 += fVal.Number
sumSqr1 += fVal.Number * fVal.Number
cnt1++
}
}
}
for i := 0; i < c2; i++ {
for j := 0; j < r2; j++ {
if fVal = mtx2[i][j]; fVal.Type == ArgNumber {
sum2 += fVal.Number
sumSqr2 += fVal.Number * fVal.Number
cnt2++
}
}
}
if cnt1 < 2.0 || cnt2 < 2.0 {
return 0, 0, false
}
if bTemplin {
fS1 := (sumSqr1 - sum1*sum1/cnt1) / (cnt1 - 1) / cnt1
fS2 := (sumSqr2 - sum2*sum2/cnt2) / (cnt2 - 1) / cnt2
if fS1+fS2 == 0 {
return 0, 0, false
}
c := fS1 / (fS1 + fS2)
return math.Abs(sum1/cnt1-sum2/cnt2) / math.Sqrt(fS1+fS2), 1 / (c*c/(cnt1-1) + (1-c)*(1-c)/(cnt2-1)), true
}
fS1 := (sumSqr1 - sum1*sum1/cnt1) / (cnt1 - 1)
fS2 := (sumSqr2 - sum2*sum2/cnt2) / (cnt2 - 1)
return math.Abs(sum1/cnt1-sum2/cnt2) / math.Sqrt((cnt1-1)*fS1+(cnt2-1)*fS2) * math.Sqrt(cnt1*cnt2*(cnt1+cnt2-2)/(cnt1+cnt2)), cnt1 + cnt2 - 2, true
}
// tTest is an implementation of the formula function TTEST.
func (fn *formulaFuncs) tTest(mtx1, mtx2 [][]formulaArg, fTails, fTyp float64) formulaArg {
var fT, fF float64
c1, c2, r1, r2, ok := len(mtx1), len(mtx2), len(mtx1[0]), len(mtx2[0]), true
if fTyp == 1 {
if c1 != c2 || r1 != r2 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
var cnt, sum1, sum2, sumSqrD float64
var fVal1, fVal2 formulaArg
for i := 0; i < c1; i++ {
for j := 0; j < r1; j++ {
fVal1, fVal2 = mtx1[i][j], mtx2[i][j]
if fVal1.Type != ArgNumber || fVal2.Type != ArgNumber {
continue
}
sum1 += fVal1.Number
sum2 += fVal2.Number
sumSqrD += (fVal1.Number - fVal2.Number) * (fVal1.Number - fVal2.Number)
cnt++
}
}
if cnt < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
sumD := sum1 - sum2
divider := cnt*sumSqrD - sumD*sumD
if divider == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
fT = math.Abs(sumD) * math.Sqrt((cnt-1)/divider)
fF = cnt - 1
} else if fTyp == 2 {
fT, fF, ok = tTest(false, mtx1, mtx2, c1, c2, r1, r2)
} else {
fT, fF, ok = tTest(true, mtx1, mtx2, c1, c2, r1, r2)
}
if !ok {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(getTDist(fT, fF, fTails))
}
// TTEST function calculates the probability associated with the Student's T
// Test, which is commonly used for identifying whether two data sets are
// likely to have come from the same two underlying populations with the same
// mean. The syntax of the function is:
//
// TTEST(array1,array2,tails,type)
func (fn *formulaFuncs) TTEST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "TTEST requires 4 arguments")
}
var array1, array2, tails, typeArg formulaArg
array1 = argsList.Front().Value.(formulaArg)
array2 = argsList.Front().Next().Value.(formulaArg)
if tails = argsList.Front().Next().Next().Value.(formulaArg); tails.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if typeArg = argsList.Back().Value.(formulaArg); typeArg.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if len(array1.Matrix) == 0 || len(array2.Matrix) == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if tails.Number != 1 && tails.Number != 2 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if typeArg.Number != 1 && typeArg.Number != 2 && typeArg.Number != 3 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return fn.tTest(array1.Matrix, array2.Matrix, tails.Number, typeArg.Number)
}
// TdotTEST function calculates the probability associated with the Student's T
// Test, which is commonly used for identifying whether two data sets are
// likely to have come from the same two underlying populations with the same
// mean. The syntax of the function is:
//
// T.TEST(array1,array2,tails,type)
func (fn *formulaFuncs) TdotTEST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "T.TEST requires 4 arguments")
}
return fn.TTEST(argsList)
}
// TRIMMEAN function calculates the trimmed mean (or truncated mean) of a
// supplied set of values. The syntax of the function is:
//
// TRIMMEAN(array,percent)
func (fn *formulaFuncs) TRIMMEAN(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "TRIMMEAN requires 2 arguments")
}
percent := argsList.Back().Value.(formulaArg).ToNumber()
if percent.Type != ArgNumber {
return percent
}
if percent.Number < 0 || percent.Number >= 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
var arr []float64
arrArg := argsList.Front().Value.(formulaArg).ToList()
for _, cell := range arrArg {
if cell.Type != ArgNumber {
continue
}
arr = append(arr, cell.Number)
}
discard := math.Floor(float64(len(arr)) * percent.Number / 2)
sort.Float64s(arr)
for i := 0; i < int(discard); i++ {
if len(arr) > 0 {
arr = arr[1:]
}
if len(arr) > 0 {
arr = arr[:len(arr)-1]
}
}
args := list.New().Init()
for _, ele := range arr {
args.PushBack(newNumberFormulaArg(ele))
}
return fn.AVERAGE(args)
}
// vars is an implementation of the formula functions VAR, VARA, VARP, VAR.P
// VAR.S and VARPA.
func (fn *formulaFuncs) vars(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
summerA, summerB, count := 0.0, 0.0, 0.0
minimum := 0.0
if name == "VAR" || name == "VAR.S" || name == "VARA" {
minimum = 1.0
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
for _, token := range arg.Value.(formulaArg).ToList() {
if token.Value() == "" {
continue
}
num := token.ToNumber()
if token.Value() != "TRUE" && num.Type == ArgNumber {
summerA += num.Number * num.Number
summerB += num.Number
count++
continue
}
num = token.ToBool()
if num.Type == ArgNumber {
summerA += num.Number * num.Number
summerB += num.Number
count++
continue
}
if name == "VARA" || name == "VARPA" {
count++
}
}
}
if count > minimum {
summerA *= count
summerB *= summerB
return newNumberFormulaArg((summerA - summerB) / (count * (count - minimum)))
}
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
// VAR function returns the sample variance of a supplied set of values. The
// syntax of the function is:
//
// VAR(number1,[number2],...)
func (fn *formulaFuncs) VAR(argsList *list.List) formulaArg {
return fn.vars("VAR", argsList)
}
// VARA function calculates the sample variance of a supplied set of values.
// The syntax of the function is:
//
// VARA(number1,[number2],...)
func (fn *formulaFuncs) VARA(argsList *list.List) formulaArg {
return fn.vars("VARA", argsList)
}
// VARP function returns the Variance of a given set of values. The syntax of
// the function is:
//
// VARP(number1,[number2],...)
func (fn *formulaFuncs) VARP(argsList *list.List) formulaArg {
return fn.vars("VARP", argsList)
}
// VARdotP function returns the Variance of a given set of values. The syntax
// of the function is:
//
// VAR.P(number1,[number2],...)
func (fn *formulaFuncs) VARdotP(argsList *list.List) formulaArg {
return fn.vars("VAR.P", argsList)
}
// VARdotS function calculates the sample variance of a supplied set of
// values. The syntax of the function is:
//
// VAR.S(number1,[number2],...)
func (fn *formulaFuncs) VARdotS(argsList *list.List) formulaArg {
return fn.vars("VAR.S", argsList)
}
// VARPA function returns the Variance of a given set of values. The syntax of
// the function is:
//
// VARPA(number1,[number2],...)
func (fn *formulaFuncs) VARPA(argsList *list.List) formulaArg {
return fn.vars("VARPA", argsList)
}
// WEIBULL function calculates the Weibull Probability Density Function or the
// Weibull Cumulative Distribution Function for a supplied set of parameters.
// The syntax of the function is:
//
// WEIBULL(x,alpha,beta,cumulative)
func (fn *formulaFuncs) WEIBULL(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL requires 4 arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
alpha := argsList.Front().Next().Value.(formulaArg).ToNumber()
beta := argsList.Back().Prev().Value.(formulaArg).ToNumber()
if alpha.Type == ArgNumber && beta.Type == ArgNumber && x.Type == ArgNumber {
if alpha.Number < 0 || alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
cumulative := argsList.Back().Value.(formulaArg).ToBool()
if cumulative.Boolean && cumulative.Number == 1 {
return newNumberFormulaArg(1 - math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number)))
}
return newNumberFormulaArg((alpha.Number / math.Pow(beta.Number, alpha.Number)) *
math.Pow(x.Number, alpha.Number-1) * math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number)))
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
// WEIBULLdotDIST function calculates the Weibull Probability Density Function
// or the Weibull Cumulative Distribution Function for a supplied set of
// parameters. The syntax of the function is:
//
// WEIBULL.DIST(x,alpha,beta,cumulative)
func (fn *formulaFuncs) WEIBULLdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL.DIST requires 4 arguments")
}
return fn.WEIBULL(argsList)
}
// ZdotTEST function calculates the one-tailed probability value of the
// Z-Test. The syntax of the function is:
//
// Z.TEST(array,x,[sigma])
func (fn *formulaFuncs) ZdotTEST(argsList *list.List) formulaArg {
argsLen := argsList.Len()
if argsLen < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST requires at least 2 arguments")
}
if argsLen > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST accepts at most 3 arguments")
}
return fn.ZTEST(argsList)
}
// ZTEST function calculates the one-tailed probability value of the Z-Test.
// The syntax of the function is:
//
// ZTEST(array,x,[sigma])
func (fn *formulaFuncs) ZTEST(argsList *list.List) formulaArg {
argsLen := argsList.Len()
if argsLen < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ZTEST requires at least 2 arguments")
}
if argsLen > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "ZTEST accepts at most 3 arguments")
}
arrArg, arrArgs := argsList.Front().Value.(formulaArg), list.New()
arrArgs.PushBack(arrArg)
arr := fn.AVERAGE(arrArgs)
if arr.Type == ArgError {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
x := argsList.Front().Next().Value.(formulaArg).ToNumber()
if x.Type == ArgError {
return x
}
sigma := argsList.Back().Value.(formulaArg).ToNumber()
if sigma.Type == ArgError {
return sigma
}
if argsLen != 3 {
sigma = fn.STDEV(arrArgs).ToNumber()
}
normsdistArg := list.New()
div := sigma.Number / math.Sqrt(float64(len(arrArg.ToList())))
if div == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
normsdistArg.PushBack(newNumberFormulaArg((arr.Number - x.Number) / div))
return newNumberFormulaArg(1 - fn.NORMSDIST(normsdistArg).Number)
}
// Information Functions
// ERRORdotTYPE function receives an error value and returns an integer, that
// tells you the type of the supplied error. The syntax of the function is:
//
// ERROR.TYPE(error_val)
func (fn *formulaFuncs) ERRORdotTYPE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ERROR.TYPE requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
if token.Type == ArgError {
for i, errType := range []string{
formulaErrorNULL, formulaErrorDIV, formulaErrorVALUE, formulaErrorREF,
formulaErrorNAME, formulaErrorNUM, formulaErrorNA,
} {
if errType == token.String {
return newNumberFormulaArg(float64(i) + 1)
}
}
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// ISBLANK function tests if a specified cell is blank (empty) and if so,
// returns TRUE; Otherwise the function returns FALSE. The syntax of the
// function is:
//
// ISBLANK(value)
func (fn *formulaFuncs) ISBLANK(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISBLANK requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgUnknown, ArgEmpty:
return newBoolFormulaArg(true)
default:
return newBoolFormulaArg(false)
}
}
// ISERR function tests if an initial supplied expression (or value) returns
// any Excel Error, except the #N/A error. If so, the function returns the
// logical value TRUE; If the supplied value is not an error or is the #N/A
// error, the ISERR function returns FALSE. The syntax of the function is:
//
// ISERR(value)
func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISERR requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
result := false
if token.Type == ArgError {
for _, errType := range []string{
formulaErrorDIV, formulaErrorNAME, formulaErrorNUM,
formulaErrorVALUE, formulaErrorREF, formulaErrorNULL,
formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA,
} {
if errType == token.String {
result = true
}
}
}
return newBoolFormulaArg(result)
}
// ISERROR function tests if an initial supplied expression (or value) returns
// an Excel Error, and if so, returns the logical value TRUE; Otherwise the
// function returns FALSE. The syntax of the function is:
//
// ISERROR(value)
func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISERROR requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
result := false
if token.Type == ArgError {
for _, errType := range []string{
formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM,
formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL,
formulaErrorCALC, formulaErrorGETTINGDATA,
} {
if errType == token.String {
result = true
}
}
}
return newBoolFormulaArg(result)
}
// ISEVEN function tests if a supplied number (or numeric expression)
// evaluates to an even number, and if so, returns TRUE; Otherwise, the
// function returns FALSE. The syntax of the function is:
//
// ISEVEN(value)
func (fn *formulaFuncs) ISEVEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISEVEN requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgEmpty:
return newBoolFormulaArg(true)
case ArgNumber, ArgString:
num := token.ToNumber()
if num.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if num.Number == 1 {
return newBoolFormulaArg(false)
}
return newBoolFormulaArg(num.Number == num.Number/2*2)
default:
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
// ISFORMULA function tests if a specified cell contains a formula, and if so,
// returns TRUE; Otherwise, the function returns FALSE. The syntax of the
// function is:
//
// ISFORMULA(reference)
func (fn *formulaFuncs) ISFORMULA(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISFORMULA requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
if arg.cellRefs != nil && arg.cellRefs.Len() == 1 {
ref := arg.cellRefs.Front().Value.(cellRef)
cell, _ := CoordinatesToCellName(ref.Col, ref.Row)
if formula, _ := fn.f.GetCellFormula(ref.Sheet, cell); len(formula) > 0 {
return newBoolFormulaArg(true)
}
}
return newBoolFormulaArg(false)
}
// ISLOGICAL function tests if a supplied value (or expression) returns a
// logical value (i.e. evaluates to True or False). If so, the function
// returns TRUE; Otherwise, it returns FALSE. The syntax of the function is:
//
// ISLOGICAL(value)
func (fn *formulaFuncs) ISLOGICAL(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISLOGICAL requires 1 argument")
}
val := argsList.Front().Value.(formulaArg).Value()
if strings.EqualFold("TRUE", val) || strings.EqualFold("FALSE", val) {
return newBoolFormulaArg(true)
}
return newBoolFormulaArg(false)
}
// ISNA function tests if an initial supplied expression (or value) returns
// the Excel #N/A Error, and if so, returns TRUE; Otherwise the function
// returns FALSE. The syntax of the function is:
//
// ISNA(value)
func (fn *formulaFuncs) ISNA(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISNA requires 1 argument")
}
if token := argsList.Front().Value.(formulaArg); token.Type == ArgError && token.String == formulaErrorNA {
return newBoolFormulaArg(true)
}
return newBoolFormulaArg(false)
}
// ISNONTEXT function tests if a supplied value is text. If not, the
// function returns TRUE; If the supplied value is text, the function returns
// FALSE. The syntax of the function is:
//
// ISNONTEXT(value)
func (fn *formulaFuncs) ISNONTEXT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISNONTEXT requires 1 argument")
}
if argsList.Front().Value.(formulaArg).Type == ArgString {
return newBoolFormulaArg(false)
}
return newBoolFormulaArg(true)
}
// ISNUMBER function tests if a supplied value is a number. If so,
// the function returns TRUE; Otherwise it returns FALSE. The syntax of the
// function is:
//
// ISNUMBER(value)
func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
arg = fn.implicitIntersect(arg)
if arg.Type == ArgNumber {
return newBoolFormulaArg(true)
}
return newBoolFormulaArg(false)
}
// ISODD function tests if a supplied number (or numeric expression) evaluates
// to an odd number, and if so, returns TRUE; Otherwise, the function returns
// FALSE. The syntax of the function is:
//
// ISODD(value)
func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISODD requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if int(arg.Number) != int(arg.Number)/2*2 {
return newBoolFormulaArg(true)
}
return newBoolFormulaArg(false)
}
// ISREF function tests if a supplied value is a reference. If so, the
// function returns TRUE; Otherwise it returns FALSE. The syntax of the
// function is:
//
// ISREF(value)
func (fn *formulaFuncs) ISREF(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISREF requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
if arg.cellRanges != nil && arg.cellRanges.Len() > 0 || arg.cellRefs != nil && arg.cellRefs.Len() > 0 {
return newBoolFormulaArg(true)
}
return newBoolFormulaArg(false)
}
// ISTEXT function tests if a supplied value is text, and if so, returns TRUE;
// Otherwise, the function returns FALSE. The syntax of the function is:
//
// ISTEXT(value)
func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
if token.ToNumber().Type != ArgError {
return newBoolFormulaArg(false)
}
return newBoolFormulaArg(token.Type == ArgString)
}
// N function converts data into a numeric value. The syntax of the function
// is:
//
// N(value)
func (fn *formulaFuncs) N(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "N requires 1 argument")
}
token, num := argsList.Front().Value.(formulaArg), 0.0
if token.Type == ArgError {
return token
}
if arg := token.ToNumber(); arg.Type == ArgNumber {
num = arg.Number
}
if token.Value() == "TRUE" {
num = 1
}
return newNumberFormulaArg(num)
}
// NA function returns the Excel #N/A error. This error message has the
// meaning 'value not available' and is produced when an Excel Formula is
// unable to find a value that it needs. The syntax of the function is:
//
// NA()
func (fn *formulaFuncs) NA(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "NA accepts no arguments")
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// SHEET function returns the Sheet number for a specified reference. The
// syntax of the function is:
//
// SHEET([value])
func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg {
if argsList.Len() > 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts at most 1 argument")
}
if argsList.Len() == 0 {
idx, _ := fn.f.GetSheetIndex(fn.sheet)
return newNumberFormulaArg(float64(idx + 1))
}
arg := argsList.Front().Value.(formulaArg)
if sheetIdx, _ := fn.f.GetSheetIndex(arg.Value()); sheetIdx != -1 {
return newNumberFormulaArg(float64(sheetIdx + 1))
}
if arg.cellRanges != nil && arg.cellRanges.Len() > 0 {
if sheetIdx, _ := fn.f.GetSheetIndex(arg.cellRanges.Front().Value.(cellRange).From.Sheet); sheetIdx != -1 {
return newNumberFormulaArg(float64(sheetIdx + 1))
}
}
if arg.cellRefs != nil && arg.cellRefs.Len() > 0 {
if sheetIdx, _ := fn.f.GetSheetIndex(arg.cellRefs.Front().Value.(cellRef).Sheet); sheetIdx != -1 {
return newNumberFormulaArg(float64(sheetIdx + 1))
}
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// SHEETS function returns the number of sheets in a supplied reference. The
// result includes sheets that are Visible, Hidden or Very Hidden. The syntax
// of the function is:
//
// SHEETS([reference])
func (fn *formulaFuncs) SHEETS(argsList *list.List) formulaArg {
if argsList.Len() > 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SHEETS accepts at most 1 argument")
}
if argsList.Len() == 0 {
return newNumberFormulaArg(float64(len(fn.f.GetSheetList())))
}
arg := argsList.Front().Value.(formulaArg)
sheetMap := map[string]struct{}{}
if arg.cellRanges != nil && arg.cellRanges.Len() > 0 {
for rng := arg.cellRanges.Front(); rng != nil; rng = rng.Next() {
sheetMap[rng.Value.(cellRange).From.Sheet] = struct{}{}
}
}
if arg.cellRefs != nil && arg.cellRefs.Len() > 0 {
for ref := arg.cellRefs.Front(); ref != nil; ref = ref.Next() {
sheetMap[ref.Value.(cellRef).Sheet] = struct{}{}
}
}
if len(sheetMap) > 0 {
return newNumberFormulaArg(float64(len(sheetMap)))
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// TYPE function returns an integer that represents the value's data type. The
// syntax of the function is:
//
// TYPE(value)
func (fn *formulaFuncs) TYPE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TYPE requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgError:
return newNumberFormulaArg(16)
case ArgMatrix:
return newNumberFormulaArg(64)
case ArgNumber, ArgEmpty:
if token.Boolean {
return newNumberFormulaArg(4)
}
return newNumberFormulaArg(1)
default:
return newNumberFormulaArg(2)
}
}
// T function tests if a supplied value is text and if so, returns the
// supplied text; Otherwise, the function returns an empty text string. The
// syntax of the function is:
//
// T(value)
func (fn *formulaFuncs) T(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "T requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
if token.Type == ArgError {
return token
}
if token.Type == ArgNumber {
return newStringFormulaArg("")
}
return newStringFormulaArg(token.Value())
}
// Logical Functions
// AND function tests a number of supplied conditions and returns TRUE or
// FALSE. The syntax of the function is:
//
// AND(logical_test1,[logical_test2],...)
func (fn *formulaFuncs) AND(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "AND requires at least 1 argument")
}
if argsList.Len() > 30 {
return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments")
}
and := true
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgUnknown:
continue
case ArgString:
if token.String == "TRUE" {
continue
}
if token.String == "FALSE" {
return newStringFormulaArg(token.String)
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
case ArgNumber:
and = and && token.Number != 0
case ArgMatrix:
// TODO
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
return newBoolFormulaArg(and)
}
// FALSE function returns the logical value FALSE. The syntax of the
// function is:
//
// FALSE()
func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments")
}
return newBoolFormulaArg(false)
}
// IFERROR function receives two values (or expressions) and tests if the
// first of these evaluates to an error. The syntax of the function is:
//
// IFERROR(value,value_if_error)
func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments")
}
value := argsList.Front().Value.(formulaArg)
if value.Type != ArgError {
if value.Type == ArgEmpty {
return newNumberFormulaArg(0)
}
return value
}
return argsList.Back().Value.(formulaArg)
}
// IFNA function tests if an initial supplied value (or expression) evaluates
// to the Excel #N/A error. If so, the function returns a second supplied
// value; Otherwise the function returns the first supplied value. The syntax
// of the function is:
//
// IFNA(value,value_if_na)
func (fn *formulaFuncs) IFNA(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IFNA requires 2 arguments")
}
arg := argsList.Front().Value.(formulaArg)
if arg.Type == ArgError && arg.String == formulaErrorNA {
return argsList.Back().Value.(formulaArg)
}
return arg
}
// IFS function tests a number of supplied conditions and returns the result
// corresponding to the first condition that evaluates to TRUE. If none of
// the supplied conditions evaluate to TRUE, the function returns the #N/A
// error.
//
// IFS(logical_test1,value_if_true1,[logical_test2,value_if_true2],...)
func (fn *formulaFuncs) IFS(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IFS requires at least 2 arguments")
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
if arg.Value.(formulaArg).ToBool().Number == 1 {
return arg.Next().Value.(formulaArg)
}
arg = arg.Next()
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// NOT function returns the opposite to a supplied logical value. The syntax
// of the function is:
//
// NOT(logical)
func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgString, ArgList:
if strings.ToUpper(token.String) == "TRUE" {
return newBoolFormulaArg(false)
}
if strings.ToUpper(token.String) == "FALSE" {
return newBoolFormulaArg(true)
}
case ArgNumber:
return newBoolFormulaArg(!(token.Number != 0))
case ArgError:
return token
}
return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument")
}
// OR function tests a number of supplied conditions and returns either TRUE
// or FALSE. The syntax of the function is:
//
// OR(logical_test1,[logical_test2],...)
func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "OR requires at least 1 argument")
}
if argsList.Len() > 30 {
return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments")
}
var or bool
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgUnknown:
continue
case ArgString:
if token.String == "FALSE" {
continue
}
if token.String == "TRUE" {
or = true
continue
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
case ArgNumber:
if or = token.Number != 0; or {
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
}
case ArgMatrix:
args := list.New()
for _, arg := range token.ToList() {
args.PushBack(arg)
}
return fn.OR(args)
}
}
return newBoolFormulaArg(or)
}
// SWITCH function compares a number of supplied values to a supplied test
// expression and returns a result corresponding to the first value that
// matches the test expression. A default value can be supplied, to be
// returned if none of the supplied values match the test expression. The
// syntax of the function is:
//
// SWITCH(expression,value1,result1,[value2,result2],[value3,result3],...,[default])
func (fn *formulaFuncs) SWITCH(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "SWITCH requires at least 3 arguments")
}
target := argsList.Front().Value.(formulaArg)
argCount := argsList.Len() - 1
switchCount := int(math.Floor(float64(argCount) / 2))
hasDefaultClause := argCount%2 != 0
result := newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
if hasDefaultClause {
result = argsList.Back().Value.(formulaArg)
}
if switchCount > 0 {
arg := argsList.Front()
for i := 0; i < switchCount; i++ {
arg = arg.Next()
if target.Value() == arg.Value.(formulaArg).Value() {
result = arg.Next().Value.(formulaArg)
break
}
arg = arg.Next()
}
}
return result
}
// TRUE function returns the logical value TRUE. The syntax of the function
// is:
//
// TRUE()
func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments")
}
return newBoolFormulaArg(true)
}
// calcXor checking if numeric cell exists and count it by given arguments
// sequence for the formula function XOR.
func calcXor(argsList *list.List) formulaArg {
count, ok := 0, false
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgError:
return token
case ArgNumber:
ok = true
if token.Number != 0 {
count++
}
case ArgMatrix:
for _, value := range token.ToList() {
if num := value.ToNumber(); num.Type == ArgNumber {
ok = true
if num.Number != 0 {
count++
}
}
}
}
}
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newBoolFormulaArg(count%2 != 0)
}
// XOR function returns the Exclusive Or logical operation for one or more
// supplied conditions. I.e. the Xor function returns TRUE if an odd number
// of the supplied conditions evaluate to TRUE, and FALSE otherwise. The
// syntax of the function is:
//
// XOR(logical_test1,[logical_test2],...)
func (fn *formulaFuncs) XOR(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "XOR requires at least 1 argument")
}
return calcXor(argsList)
}
// Date and Time Functions
// DATE returns a date, from a user-supplied year, month and day. The syntax
// of the function is:
//
// DATE(year,month,day)
func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments")
}
year := argsList.Front().Value.(formulaArg).ToNumber()
month := argsList.Front().Next().Value.(formulaArg).ToNumber()
day := argsList.Back().Value.(formulaArg).ToNumber()
if year.Type != ArgNumber || month.Type != ArgNumber || day.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments")
}
d := makeDate(int(year.Number), time.Month(month.Number), int(day.Number))
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), d) + 1)
}
// calcDateDif is an implementation of the formula function DATEDIF,
// calculation difference between two dates.
func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 {
ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]
switch unit {
case "d":
diff = endArg.Number - startArg.Number
case "md":
smMD := em
if ed < sd {
smMD--
}
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
case "ym":
diff = float64(em - sm)
if ed < sd {
diff--
}
if diff < 0 {
diff += 12
}
case "yd":
syYD := sy
if em < sm || (em == sm && ed < sd) {
syYD++
}
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
diff = s - e
}
return diff
}
// DATEDIF function calculates the number of days, months, or years between
// two dates. The syntax of the function is:
//
// DATEDIF(start_date,end_date,unit)
func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF requires 3 number arguments")
}
startArg, endArg := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber()
if startArg.Type != ArgNumber || endArg.Type != ArgNumber {
return startArg
}
if startArg.Number > endArg.Number {
return newErrorFormulaArg(formulaErrorNUM, "start_date > end_date")
}
if startArg.Number == endArg.Number {
return newNumberFormulaArg(0)
}
unit := strings.ToLower(argsList.Back().Value.(formulaArg).Value())
startDate, endDate := timeFromExcelTime(startArg.Number, false), timeFromExcelTime(endArg.Number, false)
sy, smm, sd := startDate.Date()
ey, emm, ed := endDate.Date()
sm, em, diff := int(smm), int(emm), 0.0
switch unit {
case "y":
diff = float64(ey - sy)
if em < sm || (em == sm && ed < sd) {
diff--
}
case "m":
yDiff := ey - sy
mDiff := em - sm
if ed < sd {
mDiff--
}
if mDiff < 0 {
yDiff--
mDiff += 12
}
diff = float64(yDiff*12 + mDiff)
case "d", "md", "ym", "yd":
diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg)
default:
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
}
return newNumberFormulaArg(diff)
}
// isDateOnlyFmt check if the given string matches date-only format regular expressions.
func isDateOnlyFmt(dateString string) bool {
for _, df := range dateOnlyFormats {
subMatch := df.FindStringSubmatch(dateString)
if len(subMatch) > 1 {
return true
}
}
return false
}
// isTimeOnlyFmt check if the given string matches time-only format regular expressions.
func isTimeOnlyFmt(timeString string) bool {
for _, tf := range timeFormats {
subMatch := tf.FindStringSubmatch(timeString)
if len(subMatch) > 1 {
return true
}
}
return false
}
// strToTimePatternHandler1 parse and convert the given string in pattern
// hh to the time.
func strToTimePatternHandler1(subMatch []string) (h, m int, s float64, err error) {
h, err = strconv.Atoi(subMatch[0])
return
}
// strToTimePatternHandler2 parse and convert the given string in pattern
// hh:mm to the time.
func strToTimePatternHandler2(subMatch []string) (h, m int, s float64, err error) {
if h, err = strconv.Atoi(subMatch[0]); err != nil {
return
}
m, err = strconv.Atoi(subMatch[2])
return
}
// strToTimePatternHandler3 parse and convert the given string in pattern
// mm:ss to the time.
func strToTimePatternHandler3(subMatch []string) (h, m int, s float64, err error) {
if m, err = strconv.Atoi(subMatch[0]); err != nil {
return
}
s, err = strconv.ParseFloat(subMatch[2], 64)
return
}
// strToTimePatternHandler4 parse and convert the given string in pattern
// hh:mm:ss to the time.
func strToTimePatternHandler4(subMatch []string) (h, m int, s float64, err error) {
if h, err = strconv.Atoi(subMatch[0]); err != nil {
return
}
if m, err = strconv.Atoi(subMatch[2]); err != nil {
return
}
s, err = strconv.ParseFloat(subMatch[4], 64)
return
}
// strToTime parse and convert the given string to the time.
func strToTime(str string) (int, int, float64, bool, bool, formulaArg) {
var subMatch []string
pattern := ""
for key, tf := range timeFormats {
subMatch = tf.FindStringSubmatch(str)
if len(subMatch) > 1 {
pattern = key
break
}
}
if pattern == "" {
return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
dateIsEmpty := subMatch[1] == ""
subMatch = subMatch[49:]
var (
l = len(subMatch)
last = subMatch[l-1]
am = last == "am"
pm = last == "pm"
hours, minutes int
seconds float64
err error
)
if handler, ok := map[string]func(match []string) (int, int, float64, error){
"hh": strToTimePatternHandler1,
"hh:mm": strToTimePatternHandler2,
"mm:ss": strToTimePatternHandler3,
"hh:mm:ss": strToTimePatternHandler4,
}[pattern]; ok {
if hours, minutes, seconds, err = handler(subMatch); err != nil {
return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
if minutes >= 60 {
return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if am || pm {
if hours > 12 || seconds >= 60 {
return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
} else if hours == 12 {
hours = 0
}
} else if hours >= 24 || seconds >= 10000 {
return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return hours, minutes, seconds, pm, dateIsEmpty, newEmptyFormulaArg()
}
// strToDatePatternHandler1 parse and convert the given string in pattern
// mm/dd/yy to the date.
func strToDatePatternHandler1(subMatch []string) (int, int, int, bool, error) {
var year, month, day int
var err error
if month, err = strconv.Atoi(subMatch[1]); err != nil {
return 0, 0, 0, false, err
}
if day, err = strconv.Atoi(subMatch[3]); err != nil {
return 0, 0, 0, false, err
}
if year, err = strconv.Atoi(subMatch[5]); err != nil {
return 0, 0, 0, false, err
}
if year < 0 || year > 9999 || (year > 99 && year < 1900) {
return 0, 0, 0, false, ErrParameterInvalid
}
return formatYear(year), month, day, subMatch[8] == "", err
}
// strToDatePatternHandler2 parse and convert the given string in pattern mm
// dd, yy to the date.
func strToDatePatternHandler2(subMatch []string) (int, int, int, bool, error) {
var year, month, day int
var err error
month = month2num[subMatch[1]]
if day, err = strconv.Atoi(subMatch[14]); err != nil {
return 0, 0, 0, false, err
}
if year, err = strconv.Atoi(subMatch[16]); err != nil {
return 0, 0, 0, false, err
}
if year < 0 || year > 9999 || (year > 99 && year < 1900) {
return 0, 0, 0, false, ErrParameterInvalid
}
return formatYear(year), month, day, subMatch[19] == "", err
}
// strToDatePatternHandler3 parse and convert the given string in pattern
// yy-mm-dd to the date.
func strToDatePatternHandler3(subMatch []string) (int, int, int, bool, error) {
var year, month, day int
v1, err := strconv.Atoi(subMatch[1])
if err != nil {
return 0, 0, 0, false, err
}
v2, err := strconv.Atoi(subMatch[3])
if err != nil {
return 0, 0, 0, false, err
}
v3, err := strconv.Atoi(subMatch[5])
if err != nil {
return 0, 0, 0, false, err
}
if v1 >= 1900 && v1 < 10000 {
year = v1
month = v2
day = v3
} else if v1 > 0 && v1 < 13 {
month = v1
day = v2
year = v3
} else {
return 0, 0, 0, false, ErrParameterInvalid
}
return year, month, day, subMatch[8] == "", err
}
// strToDatePatternHandler4 parse and convert the given string in pattern
// yy-mmStr-dd, yy to the date.
func strToDatePatternHandler4(subMatch []string) (int, int, int, bool, error) {
var year, month, day int
var err error
if year, err = strconv.Atoi(subMatch[16]); err != nil {
return 0, 0, 0, false, err
}
month = month2num[subMatch[3]]
if day, err = strconv.Atoi(subMatch[1]); err != nil {
return 0, 0, 0, false, err
}
return formatYear(year), month, day, subMatch[19] == "", err
}
// strToDate parse and convert the given string to the date.
func strToDate(str string) (int, int, int, bool, formulaArg) {
var subMatch []string
pattern := ""
for key, df := range dateFormats {
subMatch = df.FindStringSubmatch(str)
if len(subMatch) > 1 {
pattern = key
break
}
}
if pattern == "" {
return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
var (
timeIsEmpty bool
year, month, day int
err error
)
if handler, ok := map[string]func(match []string) (int, int, int, bool, error){
"mm/dd/yy": strToDatePatternHandler1,
"mm dd, yy": strToDatePatternHandler2,
"yy-mm-dd": strToDatePatternHandler3,
"yy-mmStr-dd": strToDatePatternHandler4,
}[pattern]; ok {
if year, month, day, timeIsEmpty, err = handler(subMatch); err != nil {
return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
if !validateDate(year, month, day) {
return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return year, month, day, timeIsEmpty, newEmptyFormulaArg()
}
// DATEVALUE function converts a text representation of a date into an Excel
// date. For example, the function converts a text string representing a
// date, into the serial number that represents the date in Excels' date-time
// code. The syntax of the function is:
//
// DATEVALUE(date_text)
func (fn *formulaFuncs) DATEVALUE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DATEVALUE requires 1 argument")
}
dateText := argsList.Front().Value.(formulaArg).Value()
if !isDateOnlyFmt(dateText) {
if _, _, _, _, _, err := strToTime(dateText); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateText)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1)
}
// DAY function returns the day of a date, represented by a serial number. The
// day is given as an integer ranging from 1 to 31. The syntax of the
// function is:
//
// DAY(serial_number)
func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DAY requires exactly 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
num := arg.ToNumber()
if num.Type != ArgNumber {
dateString := strings.ToLower(arg.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
_, _, day, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(float64(day))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "DAY only accepts positive argument")
}
if num.Number <= 60 {
return newNumberFormulaArg(math.Mod(num.Number, 31.0))
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Day()))
}
// DAYS function returns the number of days between two supplied dates. The
// syntax of the function is:
//
// DAYS(end_date,start_date)
func (fn *formulaFuncs) DAYS(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DAYS requires 2 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
end, start := args.List[0], args.List[1]
return newNumberFormulaArg(end.Number - start.Number)
}
// DAYS360 function returns the number of days between 2 dates, based on a
// 360-day year (12 x 30 months). The syntax of the function is:
//
// DAYS360(start_date,end_date,[method])
func (fn *formulaFuncs) DAYS360(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DAYS360 requires at least 2 arguments")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "DAYS360 requires at most 3 arguments")
}
startDate := toExcelDateArg(argsList.Front().Value.(formulaArg))
if startDate.Type != ArgNumber {
return startDate
}
endDate := toExcelDateArg(argsList.Front().Next().Value.(formulaArg))
if endDate.Type != ArgNumber {
return endDate
}
start, end := timeFromExcelTime(startDate.Number, false), timeFromExcelTime(endDate.Number, false)
sy, sm, sd, ey, em, ed := start.Year(), int(start.Month()), start.Day(), end.Year(), int(end.Month()), end.Day()
method := newBoolFormulaArg(false)
if argsList.Len() > 2 {
if method = argsList.Back().Value.(formulaArg).ToBool(); method.Type != ArgNumber {
return method
}
}
if method.Number == 1 {
if sd == 31 {
sd--
}
if ed == 31 {
ed--
}
} else {
if getDaysInMonth(sy, sm) == sd {
sd = 30
}
if ed > 30 {
if sd < 30 {
em++
ed = 1
} else {
ed = 30
}
}
}
return newNumberFormulaArg(float64(360*(ey-sy) + 30*(em-sm) + (ed - sd)))
}
// ISOWEEKNUM function returns the ISO week number of a supplied date. The
// syntax of the function is:
//
// ISOWEEKNUM(date)
func (fn *formulaFuncs) ISOWEEKNUM(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISOWEEKNUM requires 1 argument")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
weekNum := 0
if num.Type != ArgNumber {
dateString := strings.ToLower(date.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
_, weekNum = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC).ISOWeek()
} else {
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
_, weekNum = timeFromExcelTime(num.Number, false).ISOWeek()
}
return newNumberFormulaArg(float64(weekNum))
}
// EDATE function returns a date that is a specified number of months before or
// after a supplied start date. The syntax of function is:
//
// EDATE(start_date,months)
func (fn *formulaFuncs) EDATE(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "EDATE requires 2 arguments")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
var dateTime time.Time
if num.Type != ArgNumber {
dateString := strings.ToLower(date.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
dateTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location())
} else {
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
dateTime = timeFromExcelTime(num.Number, false)
}
month := argsList.Back().Value.(formulaArg).ToNumber()
if month.Type != ArgNumber {
return month
}
y, d := dateTime.Year(), dateTime.Day()
m := int(dateTime.Month()) + int(month.Number)
if month.Number < 0 {
y -= int(math.Ceil(-1 * float64(m) / 12))
}
if month.Number > 11 {
y += int(math.Floor(float64(m) / 12))
}
if m = m % 12; m < 0 {
m += 12
}
if d > 28 {
if days := getDaysInMonth(y, m); d > days {
d = days
}
}
result, _ := timeToExcelTime(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC), false)
return newNumberFormulaArg(result)
}
// EOMONTH function returns the last day of the month, that is a specified
// number of months before or after an initial supplied start date. The syntax
// of the function is:
//
// EOMONTH(start_date,months)
func (fn *formulaFuncs) EOMONTH(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "EOMONTH requires 2 arguments")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
var dateTime time.Time
if num.Type != ArgNumber {
dateString := strings.ToLower(date.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
dateTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location())
} else {
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
dateTime = timeFromExcelTime(num.Number, false)
}
months := argsList.Back().Value.(formulaArg).ToNumber()
if months.Type != ArgNumber {
return months
}
y, m := dateTime.Year(), int(dateTime.Month())+int(months.Number)-1
if m < 0 {
y -= int(math.Ceil(-1 * float64(m) / 12))
}
if m > 11 {
y += int(math.Floor(float64(m) / 12))
}
if m = m % 12; m < 0 {
m += 12
}
result, _ := timeToExcelTime(time.Date(y, time.Month(m+1), getDaysInMonth(y, m+1), 0, 0, 0, 0, time.UTC), false)
return newNumberFormulaArg(result)
}
// HOUR function returns an integer representing the hour component of a
// supplied Excel time. The syntax of the function is:
//
// HOUR(serial_number)
func (fn *formulaFuncs) HOUR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HOUR requires exactly 1 argument")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
if num.Type != ArgNumber {
timeString := strings.ToLower(date.Value())
if !isTimeOnlyFmt(timeString) {
_, _, _, _, err := strToDate(timeString)
if err.Type == ArgError {
return err
}
}
h, _, _, pm, _, err := strToTime(timeString)
if err.Type == ArgError {
return err
}
if pm {
h += 12
}
return newNumberFormulaArg(float64(h))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "HOUR only accepts positive argument")
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Hour()))
}
// MINUTE function returns an integer representing the minute component of a
// supplied Excel time. The syntax of the function is:
//
// MINUTE(serial_number)
func (fn *formulaFuncs) MINUTE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MINUTE requires exactly 1 argument")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
if num.Type != ArgNumber {
timeString := strings.ToLower(date.Value())
if !isTimeOnlyFmt(timeString) {
_, _, _, _, err := strToDate(timeString)
if err.Type == ArgError {
return err
}
}
_, m, _, _, _, err := strToTime(timeString)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(float64(m))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "MINUTE only accepts positive argument")
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Minute()))
}
// MONTH function returns the month of a date represented by a serial number.
// The month is given as an integer, ranging from 1 (January) to 12
// (December). The syntax of the function is:
//
// MONTH(serial_number)
func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MONTH requires exactly 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
num := arg.ToNumber()
if num.Type != ArgNumber {
dateString := strings.ToLower(arg.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
_, month, _, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(float64(month))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "MONTH only accepts positive argument")
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Month()))
}
// genWeekendMask generate weekend mask of a series of seven 0's and 1's which
// represent the seven weekdays, starting from Monday.
func genWeekendMask(weekend int) []byte {
if masks, ok := map[int][]int{
1: {5, 6}, 2: {6, 0}, 3: {0, 1}, 4: {1, 2}, 5: {2, 3}, 6: {3, 4}, 7: {4, 5},
11: {6}, 12: {0}, 13: {1}, 14: {2}, 15: {3}, 16: {4}, 17: {5},
}[weekend]; ok {
mask := make([]byte, 7)
for _, idx := range masks {
mask[idx] = 1
}
return mask
}
return nil
}
// isWorkday check if the date is workday.
func isWorkday(weekendMask []byte, date float64) bool {
dateTime := timeFromExcelTime(date, false)
weekday := dateTime.Weekday()
if weekday == time.Sunday {
weekday = 7
}
return weekendMask[weekday-1] == 0
}
// prepareWorkday returns weekend mask and workdays pre week by given days
// counted as weekend.
func prepareWorkday(weekend formulaArg) ([]byte, int) {
weekendArg := weekend.ToNumber()
if weekendArg.Type != ArgNumber {
return nil, 0
}
var weekendMask []byte
var workdaysPerWeek int
if len(weekend.Value()) == 7 {
// possible string values for the weekend argument
for _, mask := range weekend.Value() {
if mask != '0' && mask != '1' {
return nil, 0
}
weekendMask = append(weekendMask, byte(mask)-48)
}
} else {
weekendMask = genWeekendMask(int(weekendArg.Number))
}
for _, mask := range weekendMask {
if mask == 0 {
workdaysPerWeek++
}
}
return weekendMask, workdaysPerWeek
}
// toExcelDateArg function converts a text representation of a time, into an
// Excel date time number formula argument.
func toExcelDateArg(arg formulaArg) formulaArg {
num := arg.ToNumber()
if num.Type != ArgNumber {
dateString := strings.ToLower(arg.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
num.Number, _ = timeToExcelTime(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC), false)
return newNumberFormulaArg(num.Number)
}
if arg.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return num
}
// prepareHolidays function converts array type formula arguments to into an
// Excel date time number formula arguments list.
func prepareHolidays(args formulaArg) []int {
var holidays []int
for _, arg := range args.ToList() {
num := toExcelDateArg(arg)
if num.Type != ArgNumber {
continue
}
holidays = append(holidays, int(math.Ceil(num.Number)))
}
return holidays
}
// workdayIntl is an implementation of the formula function WORKDAY.INTL.
func workdayIntl(endDate, sign int, holidays []int, weekendMask []byte, startDate float64) int {
for i := 0; i < len(holidays); i++ {
holiday := holidays[i]
if sign > 0 {
if holiday > endDate {
break
}
} else {
if holiday < endDate {
break
}
}
if sign > 0 {
if holiday > int(math.Ceil(startDate)) {
if isWorkday(weekendMask, float64(holiday)) {
endDate += sign
for !isWorkday(weekendMask, float64(endDate)) {
endDate += sign
}
}
}
} else {
if holiday < int(math.Ceil(startDate)) {
if isWorkday(weekendMask, float64(holiday)) {
endDate += sign
for !isWorkday(weekendMask, float64(endDate)) {
endDate += sign
}
}
}
}
}
return endDate
}
// NETWORKDAYS function calculates the number of work days between two supplied
// dates (including the start and end date). The calculation includes all
// weekdays (Mon - Fri), excluding a supplied list of holidays. The syntax of
// the function is:
//
// NETWORKDAYS(start_date,end_date,[holidays])
func (fn *formulaFuncs) NETWORKDAYS(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS requires at least 2 arguments")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS requires at most 3 arguments")
}
args := list.New()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(argsList.Front().Next().Value.(formulaArg))
args.PushBack(newNumberFormulaArg(1))
if argsList.Len() == 3 {
args.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.NETWORKDAYSdotINTL(args)
}
// NETWORKDAYSdotINTL function calculates the number of whole work days between
// two supplied dates, excluding weekends and holidays. The function allows
// the user to specify which days are counted as weekends and holidays. The
// syntax of the function is:
//
// NETWORKDAYS.INTL(start_date,end_date,[weekend],[holidays])
func (fn *formulaFuncs) NETWORKDAYSdotINTL(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS.INTL requires at least 2 arguments")
}
if argsList.Len() > 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NETWORKDAYS.INTL requires at most 4 arguments")
}
startDate := toExcelDateArg(argsList.Front().Value.(formulaArg))
if startDate.Type != ArgNumber {
return startDate
}
endDate := toExcelDateArg(argsList.Front().Next().Value.(formulaArg))
if endDate.Type != ArgNumber {
return endDate
}
weekend := newNumberFormulaArg(1)
if argsList.Len() > 2 {
weekend = argsList.Front().Next().Next().Value.(formulaArg)
}
var holidays []int
if argsList.Len() == 4 {
holidays = prepareHolidays(argsList.Back().Value.(formulaArg))
sort.Ints(holidays)
}
weekendMask, workdaysPerWeek := prepareWorkday(weekend)
if workdaysPerWeek == 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
sign := 1
if startDate.Number > endDate.Number {
sign = -1
startDate.Number, endDate.Number = endDate.Number, startDate.Number
}
offset := endDate.Number - startDate.Number
count := int(math.Floor(offset/7) * float64(workdaysPerWeek))
daysMod := int(offset) % 7
for daysMod >= 0 {
if isWorkday(weekendMask, endDate.Number-float64(daysMod)) {
count++
}
daysMod--
}
for i := 0; i < len(holidays); i++ {
holiday := float64(holidays[i])
if isWorkday(weekendMask, holiday) && holiday >= startDate.Number && holiday <= endDate.Number {
count--
}
}
return newNumberFormulaArg(float64(sign * count))
}
// WORKDAY function returns a date that is a supplied number of working days
// (excluding weekends and holidays) ahead of a given start date. The syntax
// of the function is:
//
// WORKDAY(start_date,days,[holidays])
func (fn *formulaFuncs) WORKDAY(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY requires at least 2 arguments")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY requires at most 3 arguments")
}
args := list.New()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(argsList.Front().Next().Value.(formulaArg))
args.PushBack(newNumberFormulaArg(1))
if argsList.Len() == 3 {
args.PushBack(argsList.Back().Value.(formulaArg))
}
return fn.WORKDAYdotINTL(args)
}
// WORKDAYdotINTL function returns a date that is a supplied number of working
// days (excluding weekends and holidays) ahead of a given start date. The
// function allows the user to specify which days of the week are counted as
// weekends. The syntax of the function is:
//
// WORKDAY.INTL(start_date,days,[weekend],[holidays])
func (fn *formulaFuncs) WORKDAYdotINTL(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at least 2 arguments")
}
if argsList.Len() > 4 {
return newErrorFormulaArg(formulaErrorVALUE, "WORKDAY.INTL requires at most 4 arguments")
}
startDate := toExcelDateArg(argsList.Front().Value.(formulaArg))
if startDate.Type != ArgNumber {
return startDate
}
days := argsList.Front().Next().Value.(formulaArg).ToNumber()
if days.Type != ArgNumber {
return days
}
weekend := newNumberFormulaArg(1)
if argsList.Len() > 2 {
weekend = argsList.Front().Next().Next().Value.(formulaArg)
}
var holidays []int
if argsList.Len() == 4 {
holidays = prepareHolidays(argsList.Back().Value.(formulaArg))
sort.Ints(holidays)
}
if days.Number == 0 {
return newNumberFormulaArg(math.Ceil(startDate.Number))
}
weekendMask, workdaysPerWeek := prepareWorkday(weekend)
if workdaysPerWeek == 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
sign := 1
if days.Number < 0 {
sign = -1
}
offset := int(days.Number) / workdaysPerWeek
daysMod := int(days.Number) % workdaysPerWeek
endDate := int(math.Ceil(startDate.Number)) + offset*7
if daysMod == 0 {
for !isWorkday(weekendMask, float64(endDate)) {
endDate -= sign
}
} else {
for daysMod != 0 {
endDate += sign
if isWorkday(weekendMask, float64(endDate)) {
if daysMod < 0 {
daysMod++
continue
}
daysMod--
}
}
}
return newNumberFormulaArg(float64(workdayIntl(endDate, sign, holidays, weekendMask, startDate.Number)))
}
// YEAR function returns an integer representing the year of a supplied date.
// The syntax of the function is:
//
// YEAR(serial_number)
func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "YEAR requires exactly 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
num := arg.ToNumber()
if num.Type != ArgNumber {
dateString := strings.ToLower(arg.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
year, _, _, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(float64(year))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "YEAR only accepts positive argument")
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Year()))
}
// yearFracBasisCond is an implementation of the yearFracBasis1.
func yearFracBasisCond(sy, sm, sd, ey, em, ed int) bool {
return (isLeapYear(sy) && (sm < 2 || (sm == 2 && sd <= 29))) || (isLeapYear(ey) && (em > 2 || (em == 2 && ed == 29)))
}
// yearFracBasis0 function returns the fraction of a year that between two
// supplied dates in US (NASD) 30/360 type of day.
func yearFracBasis0(startDate, endDate float64) (dayDiff, daysInYear float64) {
startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false)
sy, smM, sd := startTime.Date()
ey, emM, ed := endTime.Date()
sm, em := int(smM), int(emM)
if sd == 31 {
sd--
}
if sd == 30 && ed == 31 {
ed--
} else if leap := isLeapYear(sy); sm == 2 && ((leap && sd == 29) || (!leap && sd == 28)) {
sd = 30
if leap := isLeapYear(ey); em == 2 && ((leap && ed == 29) || (!leap && ed == 28)) {
ed = 30
}
}
dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd))
daysInYear = 360
return
}
// yearFracBasis1 function returns the fraction of a year that between two
// supplied dates in actual type of day.
func yearFracBasis1(startDate, endDate float64) (dayDiff, daysInYear float64) {
startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false)
sy, smM, sd := startTime.Date()
ey, emM, ed := endTime.Date()
sm, em := int(smM), int(emM)
dayDiff = endDate - startDate
isYearDifferent := sy != ey
if isYearDifferent && (ey != sy+1 || sm < em || (sm == em && sd < ed)) {
dayCount := 0
for y := sy; y <= ey; y++ {
dayCount += getYearDays(y, 1)
}
daysInYear = float64(dayCount) / float64(ey-sy+1)
} else {
if !isYearDifferent && isLeapYear(sy) {
daysInYear = 366
} else {
if isYearDifferent && yearFracBasisCond(sy, sm, sd, ey, em, ed) {
daysInYear = 366
} else {
daysInYear = 365
}
}
}
return
}
// yearFracBasis4 function returns the fraction of a year that between two
// supplied dates in European 30/360 type of day.
func yearFracBasis4(startDate, endDate float64) (dayDiff, daysInYear float64) {
startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false)
sy, smM, sd := startTime.Date()
ey, emM, ed := endTime.Date()
sm, em := int(smM), int(emM)
if sd == 31 {
sd--
}
if ed == 31 {
ed--
}
dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd))
daysInYear = 360
return
}
// yearFrac is an implementation of the formula function YEARFRAC.
func yearFrac(startDate, endDate float64, basis int) formulaArg {
startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false)
if startTime.Equal(endTime) {
return newNumberFormulaArg(0)
}
var dayDiff, daysInYear float64
switch basis {
case 0:
dayDiff, daysInYear = yearFracBasis0(startDate, endDate)
case 1:
dayDiff, daysInYear = yearFracBasis1(startDate, endDate)
case 2:
dayDiff = endDate - startDate
daysInYear = 360
case 3:
dayDiff = endDate - startDate
daysInYear = 365
case 4:
dayDiff, daysInYear = yearFracBasis4(startDate, endDate)
default:
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
return newNumberFormulaArg(dayDiff / daysInYear)
}
// getYearDays return days of the year with specifying the type of day count
// basis to be used.
func getYearDays(year, basis int) int {
switch basis {
case 1:
if isLeapYear(year) {
return 366
}
return 365
case 3:
return 365
default:
return 360
}
}
// YEARFRAC function returns the fraction of a year that is represented by the
// number of whole days between two supplied dates. The syntax of the
// function is:
//
// YEARFRAC(start_date,end_date,[basis])
func (fn *formulaFuncs) YEARFRAC(argsList *list.List) formulaArg {
if argsList.Len() != 2 && argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "YEARFRAC requires 3 or 4 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
start, end := args.List[0], args.List[1]
basis := newNumberFormulaArg(0)
if argsList.Len() == 3 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return basis
}
}
return yearFrac(start.Number, end.Number, int(basis.Number))
}
// NOW function returns the current date and time. The function receives no
// arguments and therefore. The syntax of the function is:
//
// NOW()
func (fn *formulaFuncs) NOW(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "NOW accepts no arguments")
}
now := time.Now()
_, offset := now.Zone()
return newNumberFormulaArg(25569.0 + float64(now.Unix()+int64(offset))/86400)
}
// SECOND function returns an integer representing the second component of a
// supplied Excel time. The syntax of the function is:
//
// SECOND(serial_number)
func (fn *formulaFuncs) SECOND(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "SECOND requires exactly 1 argument")
}
date := argsList.Front().Value.(formulaArg)
num := date.ToNumber()
if num.Type != ArgNumber {
timeString := strings.ToLower(date.Value())
if !isTimeOnlyFmt(timeString) {
_, _, _, _, err := strToDate(timeString)
if err.Type == ArgError {
return err
}
}
_, _, s, _, _, err := strToTime(timeString)
if err.Type == ArgError {
return err
}
return newNumberFormulaArg(float64(int(s) % 60))
}
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "SECOND only accepts positive argument")
}
return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Second()))
}
// TIME function accepts three integer arguments representing hours, minutes
// and seconds, and returns an Excel time. I.e. the function returns the
// decimal value that represents the time in Excel. The syntax of the
// function is:
//
// TIME(hour,minute,second)
func (fn *formulaFuncs) TIME(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments")
}
h := argsList.Front().Value.(formulaArg).ToNumber()
m := argsList.Front().Next().Value.(formulaArg).ToNumber()
s := argsList.Back().Value.(formulaArg).ToNumber()
if h.Type != ArgNumber || m.Type != ArgNumber || s.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments")
}
t := (h.Number*3600 + m.Number*60 + s.Number) / 86400
if t < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(t)
}
// TIMEVALUE function converts a text representation of a time, into an Excel
// time. The syntax of the function is:
//
// TIMEVALUE(time_text)
func (fn *formulaFuncs) TIMEVALUE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TIMEVALUE requires exactly 1 argument")
}
date := argsList.Front().Value.(formulaArg)
timeString := strings.ToLower(date.Value())
if !isTimeOnlyFmt(timeString) {
_, _, _, _, err := strToDate(timeString)
if err.Type == ArgError {
return err
}
}
h, m, s, pm, _, err := strToTime(timeString)
if err.Type == ArgError {
return err
}
if pm {
h += 12
}
args := list.New()
args.PushBack(newNumberFormulaArg(float64(h)))
args.PushBack(newNumberFormulaArg(float64(m)))
args.PushBack(newNumberFormulaArg(s))
return fn.TIME(args)
}
// TODAY function returns the current date. The function has no arguments and
// therefore. The syntax of the function is:
//
// TODAY()
func (fn *formulaFuncs) TODAY(argsList *list.List) formulaArg {
if argsList.Len() != 0 {
return newErrorFormulaArg(formulaErrorVALUE, "TODAY accepts no arguments")
}
now := time.Now()
_, offset := now.Zone()
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), now.Unix()+int64(offset)) + 1)
}
// makeDate return date as a Unix time, the number of seconds elapsed since
// January 1, 1970 UTC.
func makeDate(y int, m time.Month, d int) int64 {
if y == 1900 && int(m) <= 2 {
d--
}
date := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
return date.Unix()
}
// daysBetween return time interval of the given start timestamp and end
// timestamp.
func daysBetween(startDate, endDate int64) float64 {
return float64(int(0.5 + float64((endDate-startDate)/86400)))
}
// WEEKDAY function returns an integer representing the day of the week for a
// supplied date. The syntax of the function is:
//
// WEEKDAY(serial_number,[return_type])
func (fn *formulaFuncs) WEEKDAY(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY allows at most 2 arguments")
}
sn := argsList.Front().Value.(formulaArg)
num := sn.ToNumber()
weekday, returnType := 0, 1
if num.Type != ArgNumber {
dateString := strings.ToLower(sn.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
weekday = int(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()).Weekday())
} else {
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
weekday = int(timeFromExcelTime(num.Number, false).Weekday())
}
if argsList.Len() == 2 {
returnTypeArg := argsList.Back().Value.(formulaArg).ToNumber()
if returnTypeArg.Type != ArgNumber {
return returnTypeArg
}
returnType = int(returnTypeArg.Number)
}
if returnType == 2 {
returnType = 11
}
weekday++
if returnType == 1 {
return newNumberFormulaArg(float64(weekday))
}
if returnType == 3 {
return newNumberFormulaArg(float64((weekday + 6 - 1) % 7))
}
if returnType >= 11 && returnType <= 17 {
return newNumberFormulaArg(float64((weekday+6-(returnType-10))%7 + 1))
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
// weeknum is an implementation of the formula function WEEKNUM.
func (fn *formulaFuncs) weeknum(snTime time.Time, returnType int) formulaArg {
days := snTime.YearDay()
weekMod, weekNum := days%7, math.Ceil(float64(days)/7)
if weekMod == 0 {
weekMod = 7
}
year := snTime.Year()
firstWeekday := int(time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC).Weekday())
var offset int
switch returnType {
case 1, 17:
offset = 0
case 2, 11, 21:
offset = 1
case 12, 13, 14, 15, 16:
offset = returnType - 10
default:
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
padding := offset + 7 - firstWeekday
if padding > 7 {
padding -= 7
}
if weekMod > padding {
weekNum++
}
if returnType == 21 && (firstWeekday == 0 || firstWeekday > 4) {
if weekNum--; weekNum < 1 {
if weekNum = 52; int(time.Date(year-1, time.January, 1, 0, 0, 0, 0, time.UTC).Weekday()) < 4 {
weekNum++
}
}
}
return newNumberFormulaArg(weekNum)
}
// WEEKNUM function returns an integer representing the week number (from 1 to
// 53) of the year. The syntax of the function is:
//
// WEEKNUM(serial_number,[return_type])
func (fn *formulaFuncs) WEEKNUM(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "WEEKNUM requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "WEEKNUM allows at most 2 arguments")
}
sn := argsList.Front().Value.(formulaArg)
num, returnType := sn.ToNumber(), 1
var snTime time.Time
if num.Type != ArgNumber {
dateString := strings.ToLower(sn.Value())
if !isDateOnlyFmt(dateString) {
if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError {
return err
}
}
y, m, d, _, err := strToDate(dateString)
if err.Type == ArgError {
return err
}
snTime = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location())
} else {
if num.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
snTime = timeFromExcelTime(num.Number, false)
}
if argsList.Len() == 2 {
returnTypeArg := argsList.Back().Value.(formulaArg).ToNumber()
if returnTypeArg.Type != ArgNumber {
return returnTypeArg
}
returnType = int(returnTypeArg.Number)
}
return fn.weeknum(snTime, returnType)
}
// Text Functions
// prepareToText checking and prepare arguments for the formula functions
// ARRAYTOTEXT and VALUETOTEXT.
func prepareToText(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
}
format := newNumberFormulaArg(0)
if argsList.Len() == 2 {
if format = argsList.Back().Value.(formulaArg).ToNumber(); format.Type != ArgNumber {
return format
}
}
if format.Number != 0 && format.Number != 1 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return format
}
// ARRAYTOTEXT function returns an array of text values from any specified
// range. It passes text values unchanged, and converts non-text values to
// text. The syntax of the function is:
//
// ARRAYTOTEXT(array,[format])
func (fn *formulaFuncs) ARRAYTOTEXT(argsList *list.List) formulaArg {
var mtx [][]string
format := prepareToText("ARRAYTOTEXT", argsList)
if format.Type != ArgNumber {
return format
}
for _, rows := range argsList.Front().Value.(formulaArg).Matrix {
var row []string
for _, cell := range rows {
if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 {
row = append(row, fmt.Sprintf("\"%s\"", cell.Value()))
continue
}
row = append(row, cell.Value())
}
mtx = append(mtx, row)
}
var text []string
for _, row := range mtx {
if format.Number == 1 {
text = append(text, strings.Join(row, ","))
continue
}
text = append(text, strings.Join(row, ", "))
}
if format.Number == 1 {
return newStringFormulaArg(fmt.Sprintf("{%s}", strings.Join(text, ";")))
}
return newStringFormulaArg(strings.Join(text, ", "))
}
// bahttextAppendDigit appends a digit to the passed string.
func bahttextAppendDigit(text string, digit int) string {
if 0 <= digit && digit <= 9 {
return text + []string{th0, th1, th2, th3, th4, th5, th6, th7, th8, th9}[digit]
}
return text
}
// bahttextAppendPow10 appends a value raised to a power of 10: digit*10^pow10.
func bahttextAppendPow10(text string, digit, pow10 int) string {
text = bahttextAppendDigit(text, digit)
switch pow10 {
case 2:
text += th1e2
case 3:
text += th1e3
case 4:
text += th1e4
case 5:
text += th1e5
}
return text
}
// bahttextAppendBlock appends a block of 6 digits to the passed string.
func bahttextAppendBlock(text string, val int) string {
if val >= 100000 {
text = bahttextAppendPow10(text, val/100000, 5)
val %= 100000
}
if val >= 10000 {
text = bahttextAppendPow10(text, val/10000, 4)
val %= 10000
}
if val >= 1000 {
text = bahttextAppendPow10(text, val/1000, 3)
val %= 1000
}
if val >= 100 {
text = bahttextAppendPow10(text, val/100, 2)
val %= 100
}
if val > 0 {
n10 := val / 10
n1 := val % 10
if n10 >= 1 {
if n10 >= 3 {
text = bahttextAppendDigit(text, n10)
} else if n10 == 2 {
text += th20
}
text += th10
}
if n10 > 0 && n1 == 1 {
text += th11
} else if n1 > 0 {
text = bahttextAppendDigit(text, n1)
}
}
return text
}
// BAHTTEXT function converts a number into Thai text, with the suffix "Baht".
// The syntax of the function is:
//
// BAHTTEXT(number)
func (fn *formulaFuncs) BAHTTEXT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "BAHTTEXT requires 1 numeric argument")
}
token := argsList.Front().Value.(formulaArg)
number := token.ToNumber()
if number.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, number.Error)
}
minus := number.Number < 0
num := math.Floor(math.Abs(number.Number)*100 + 0.5)
// split baht and satang
splitBlock := func(val, size float64) (float64, int) {
integer, frac := math.Modf((val + 0.1) / size)
frac = frac*size + 0.1
return integer, int(frac)
}
baht, satang := splitBlock(num, 100)
var text string
if baht == 0 {
if satang == 0 {
text += th0
}
} else {
for baht > 0 {
var block string
var nBlock int
baht, nBlock = splitBlock(baht, 1.0e6)
if nBlock > 0 {
block = bahttextAppendBlock(block, nBlock)
}
if baht > 0 {
block = th1e6 + block
}
text = block + text
}
}
if len(text) > 0 {
text += thBaht
}
if satang == 0 {
text += thDot0
} else {
text = bahttextAppendBlock(text, satang)
text += thSatang
}
if minus {
text = thMinus + text
}
return newStringFormulaArg(text)
}
// CHAR function returns the character relating to a supplied character set
// number (from 1 to 255). The syntax of the function is:
//
// CHAR(number)
func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "CHAR requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type != ArgNumber {
return arg
}
num := int(arg.Number)
if num < 0 || num > MaxFieldLength {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newStringFormulaArg(fmt.Sprintf("%c", num))
}
// CLEAN removes all non-printable characters from a supplied text string. The
// syntax of the function is:
//
// CLEAN(text)
func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument")
}
b := bytes.Buffer{}
for _, c := range argsList.Front().Value.(formulaArg).Value() {
if c > 31 {
b.WriteRune(c)
}
}
return newStringFormulaArg(b.String())
}
// CODE function converts the first character of a supplied text string into
// the associated numeric character set code used by your computer. The
// syntax of the function is:
//
// CODE(text)
func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg {
return fn.code("CODE", argsList)
}
// code is an implementation of the formula functions CODE and UNICODE.
func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name))
}
text := argsList.Front().Value.(formulaArg).Value()
if len(text) == 0 {
if name == "CODE" {
return newNumberFormulaArg(0)
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newNumberFormulaArg(float64(text[0]))
}
// CONCAT function joins together a series of supplied text strings into one
// combined text string.
//
// CONCAT(text1,[text2],...)
func (fn *formulaFuncs) CONCAT(argsList *list.List) formulaArg {
return fn.concat(argsList)
}
// CONCATENATE function joins together a series of supplied text strings into
// one combined text string.
//
// CONCATENATE(text1,[text2],...)
func (fn *formulaFuncs) CONCATENATE(argsList *list.List) formulaArg {
return fn.concat(argsList)
}
// concat is an implementation of the formula functions CONCAT and
// CONCATENATE.
func (fn *formulaFuncs) concat(argsList *list.List) formulaArg {
var buf bytes.Buffer
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
for _, cell := range arg.Value.(formulaArg).ToList() {
if cell.Type == ArgError {
return cell
}
buf.WriteString(cell.Value())
}
}
return newStringFormulaArg(buf.String())
}
// DBCS converts half-width (single-byte) letters within a character string to
// full-width (double-byte) characters. The syntax of the function is:
//
// DBCS(text)
func (fn *formulaFuncs) DBCS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "DBCS requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
if arg.Type == ArgError {
return arg
}
if fn.f.options.CultureInfo == CultureNameJaJP ||
fn.f.options.CultureInfo == CultureNameZhCN ||
fn.f.options.CultureInfo == CultureNameZhTW {
var chars []string
for _, r := range arg.Value() {
code := r
if code == 32 {
code = 12288
} else {
code += 65248
}
if (code < 32 || code > 126) && r != 165 && code < 65381 {
chars = append(chars, string(code))
} else {
chars = append(chars, string(r))
}
}
return newStringFormulaArg(strings.Join(chars, ""))
}
return arg
}
// EXACT function tests if two supplied text strings or values are exactly
// equal and if so, returns TRUE; Otherwise, the function returns FALSE. The
// function is case-sensitive. The syntax of the function is:
//
// EXACT(text1,text2)
func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments")
}
text1 := argsList.Front().Value.(formulaArg).Value()
text2 := argsList.Back().Value.(formulaArg).Value()
return newBoolFormulaArg(text1 == text2)
}
// FIXED function rounds a supplied number to a specified number of decimal
// places and then converts this into text. The syntax of the function is:
//
// FIXED(number,[decimals],[no_commas])
func (fn *formulaFuncs) FIXED(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FIXED requires at least 1 argument")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "FIXED allows at most 3 arguments")
}
numArg := argsList.Front().Value.(formulaArg).ToNumber()
if numArg.Type != ArgNumber {
return numArg
}
precision, decimals, noCommas := 0, 0, false
s := strings.Split(argsList.Front().Value.(formulaArg).Value(), ".")
if argsList.Len() == 1 && len(s) == 2 {
precision = len(s[1])
decimals = len(s[1])
}
if argsList.Len() >= 2 {
decimalsArg := argsList.Front().Next().Value.(formulaArg).ToNumber()
if decimalsArg.Type != ArgNumber {
return decimalsArg
}
decimals = int(decimalsArg.Number)
}
if argsList.Len() == 3 {
noCommasArg := argsList.Back().Value.(formulaArg).ToBool()
if noCommasArg.Type == ArgError {
return noCommasArg
}
noCommas = noCommasArg.Boolean
}
n := math.Pow(10, float64(decimals))
r := numArg.Number * n
fixed := float64(int(r+math.Copysign(0.5, r))) / n
if decimals > 0 {
precision = decimals
}
if noCommas {
return newStringFormulaArg(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), fixed))
}
p := message.NewPrinter(language.English)
return newStringFormulaArg(p.Sprintf(fmt.Sprintf("%%.%df", precision), fixed))
}
// FIND function returns the position of a specified character or sub-string
// within a supplied text string. The function is case-sensitive. The syntax
// of the function is:
//
// FIND(find_text,within_text,[start_num])
func (fn *formulaFuncs) FIND(argsList *list.List) formulaArg {
return fn.find("FIND", argsList)
}
// FINDB counts each double-byte character as 2 when you have enabled the
// editing of a language that supports DBCS and then set it as the default
// language. Otherwise, FINDB counts each character as 1. The syntax of the
// function is:
//
// FINDB(find_text,within_text,[start_num])
func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg {
return fn.find("FINDB", argsList)
}
// prepareFindArgs checking and prepare arguments for the formula functions
// FIND, FINDB, SEARCH and SEARCHB.
func (fn *formulaFuncs) prepareFindArgs(name string, argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name))
}
startNum := 1
if argsList.Len() == 3 {
numArg := argsList.Back().Value.(formulaArg).ToNumber()
if numArg.Type != ArgNumber {
return numArg
}
if numArg.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
startNum = int(numArg.Number)
}
return newListFormulaArg([]formulaArg{newNumberFormulaArg(float64(startNum))})
}
// find is an implementation of the formula functions FIND, FINDB, SEARCH and
// SEARCHB.
func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
args := fn.prepareFindArgs(name, argsList)
if args.Type != ArgList {
return args
}
findTextArg := argsList.Front().Value.(formulaArg)
withinText := argsList.Front().Next().Value.(formulaArg).Value()
startNum := int(args.List[0].Number)
dbcs, search := name == "FINDB" || name == "SEARCHB", name == "SEARCH" || name == "SEARCHB"
find := func(findText string) formulaArg {
if findText == "" {
return newNumberFormulaArg(float64(startNum))
}
if search {
findText, withinText = strings.ToUpper(findText), strings.ToUpper(withinText)
}
offset, ok := matchPattern(findText, withinText, dbcs, startNum)
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
result := offset
if dbcs {
var pre int
for idx := range withinText {
if pre > offset {
break
}
if idx-pre > 1 {
result++
}
pre = idx
}
}
return newNumberFormulaArg(float64(result))
}
if findTextArg.Type == ArgMatrix {
var mtx [][]formulaArg
for _, row := range findTextArg.Matrix {
var array []formulaArg
for _, findText := range row {
array = append(array, find(findText.Value()))
}
mtx = append(mtx, array)
}
return newMatrixFormulaArg(mtx)
}
return find(findTextArg.Value())
}
// LEFT function returns a specified number of characters from the start of a
// supplied text string. The syntax of the function is:
//
// LEFT(text,[num_chars])
func (fn *formulaFuncs) LEFT(argsList *list.List) formulaArg {
return fn.leftRight("LEFT", argsList)
}
// LEFTB returns the first character or characters in a text string, based on
// the number of bytes you specify. The syntax of the function is:
//
// LEFTB(text,[num_bytes])
func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg {
return fn.leftRight("LEFTB", argsList)
}
// leftRight is an implementation of the formula functions LEFT, LEFTB, RIGHT,
// RIGHTB.
func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
}
text, numChars := argsList.Front().Value.(formulaArg).Value(), 1
if argsList.Len() == 2 {
numArg := argsList.Back().Value.(formulaArg).ToNumber()
if numArg.Type != ArgNumber {
return numArg
}
if numArg.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
numChars = int(numArg.Number)
}
if name == "LEFTB" || name == "RIGHTB" {
if len(text) > numChars {
if name == "LEFTB" {
return newStringFormulaArg(text[:numChars])
}
// RIGHTB
return newStringFormulaArg(text[len(text)-numChars:])
}
return newStringFormulaArg(text)
}
// LEFT/RIGHT
if countUTF16String(text) > numChars {
if name == "LEFT" {
return newStringFormulaArg(truncateUTF16Units(text, numChars))
}
// RIGHT
return newStringFormulaArg(string([]rune(text)[utf8.RuneCountInString(text)-numChars:]))
}
return newStringFormulaArg(text)
}
// LEN returns the length of a supplied text string. The syntax of the
// function is:
//
// LEN(text)
func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument")
}
return newNumberFormulaArg(float64(countUTF16String(argsList.Front().Value.(formulaArg).Value())))
}
// LENB returns the number of bytes used to represent the characters in a text
// string. LENB counts 2 bytes per character only when a DBCS language is set
// as the default language. Otherwise LENB behaves the same as LEN, counting
// 1 byte per character. The syntax of the function is:
//
// LENB(text)
func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument")
}
result := 0
for _, r := range argsList.Front().Value.(formulaArg).Value() {
b := utf8.RuneLen(r)
if b == 1 {
result++
} else if b > 1 {
result += 2
}
}
return newNumberFormulaArg(float64(result))
}
// LOWER converts all characters in a supplied text string to lower case. The
// syntax of the function is:
//
// LOWER(text)
func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument")
}
return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).Value()))
}
// MID function returns a specified number of characters from the middle of a
// supplied text string. The syntax of the function is:
//
// MID(text,start_num,num_chars)
func (fn *formulaFuncs) MID(argsList *list.List) formulaArg {
return fn.mid("MID", argsList)
}
// MIDB returns a specific number of characters from a text string, starting
// at the position you specify, based on the number of bytes you specify. The
// syntax of the function is:
//
// MID(text,start_num,num_chars)
func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg {
return fn.mid("MIDB", argsList)
}
// mid is an implementation of the formula functions MID and MIDB.
func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name))
}
text := argsList.Front().Value.(formulaArg).Value()
startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if startNumArg.Type != ArgNumber {
return startNumArg
}
if numCharsArg.Type != ArgNumber {
return numCharsArg
}
startNum := int(startNumArg.Number)
if startNum < 1 || numCharsArg.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if name == "MIDB" {
var result string
var cnt, offset int
for _, char := range text {
offset++
var dbcs bool
if utf8.RuneLen(char) > 1 {
dbcs = true
offset++
}
if cnt == int(numCharsArg.Number) {
break
}
if offset+1 > startNum {
if dbcs {
if cnt+2 > int(numCharsArg.Number) {
result += string(char)[:1]
break
}
result += string(char)
cnt += 2
} else {
result += string(char)
cnt++
}
}
}
return newStringFormulaArg(result)
}
// MID
textLen := countUTF16String(text)
if startNum > textLen {
return newStringFormulaArg("")
}
startNum--
endNum := startNum + int(numCharsArg.Number)
if endNum > textLen+1 {
return newStringFormulaArg(string([]rune(text)[startNum:]))
}
return newStringFormulaArg(string([]rune(text)[startNum:endNum]))
}
// PROPER converts all characters in a supplied text string to proper case
// (i.e. all letters that do not immediately follow another letter are set to
// upper case and all other characters are lower case). The syntax of the
// function is:
//
// PROPER(text)
func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "PROPER requires 1 argument")
}
buf := bytes.Buffer{}
isLetter := false
for _, char := range argsList.Front().Value.(formulaArg).Value() {
if !isLetter && unicode.IsLetter(char) {
buf.WriteRune(unicode.ToUpper(char))
} else {
buf.WriteRune(unicode.ToLower(char))
}
isLetter = unicode.IsLetter(char)
}
return newStringFormulaArg(buf.String())
}
// REPLACE function replaces all or part of a text string with another string.
// The syntax of the function is:
//
// REPLACE(old_text,start_num,num_chars,new_text)
func (fn *formulaFuncs) REPLACE(argsList *list.List) formulaArg {
return fn.replace("REPLACE", argsList)
}
// REPLACEB replaces part of a text string, based on the number of bytes you
// specify, with a different text string.
//
// REPLACEB(old_text,start_num,num_chars,new_text)
func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg {
return fn.replace("REPLACEB", argsList)
}
// replace is an implementation of the formula functions REPLACE and REPLACEB.
// TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
// (Traditional), and Korean.
func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 arguments", name))
}
sourceText, targetText := argsList.Front().Value.(formulaArg).Value(), argsList.Back().Value.(formulaArg).Value()
startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if startNumArg.Type != ArgNumber {
return startNumArg
}
if numCharsArg.Type != ArgNumber {
return numCharsArg
}
sourceTextLen, startIdx := len(sourceText), int(startNumArg.Number)
if startIdx > sourceTextLen {
startIdx = sourceTextLen + 1
}
endIdx := startIdx + int(numCharsArg.Number)
if endIdx > sourceTextLen {
endIdx = sourceTextLen + 1
}
if startIdx < 1 || endIdx < 1 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
result := sourceText[:startIdx-1] + targetText + sourceText[endIdx-1:]
return newStringFormulaArg(result)
}
// REPT function returns a supplied text string, repeated a specified number
// of times. The syntax of the function is:
//
// REPT(text,number_times)
func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments")
}
text := argsList.Front().Value.(formulaArg)
if text.Type != ArgString {
return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string")
}
times := argsList.Back().Value.(formulaArg).ToNumber()
if times.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number")
}
if times.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0")
}
if times.Number == 0 {
return newStringFormulaArg("")
}
buf := bytes.Buffer{}
for i := 0; i < int(times.Number); i++ {
buf.WriteString(text.Value())
}
return newStringFormulaArg(buf.String())
}
// RIGHT function returns a specified number of characters from the end of a
// supplied text string. The syntax of the function is:
//
// RIGHT(text,[num_chars])
func (fn *formulaFuncs) RIGHT(argsList *list.List) formulaArg {
return fn.leftRight("RIGHT", argsList)
}
// RIGHTB returns the last character or characters in a text string, based on
// the number of bytes you specify. The syntax of the function is:
//
// RIGHTB(text,[num_bytes])
func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg {
return fn.leftRight("RIGHTB", argsList)
}
// SEARCH function returns the position of a specified character or sub-string
// within a supplied text string. The syntax of the function is:
//
// SEARCH(search_text,within_text,[start_num])
func (fn *formulaFuncs) SEARCH(argsList *list.List) formulaArg {
return fn.find("SEARCH", argsList)
}
// SEARCHB functions locate one text string within a second text string, and
// return the number of the starting position of the first text string from the
// first character of the second text string. The syntax of the function is:
//
// SEARCHB(search_text,within_text,[start_num])
func (fn *formulaFuncs) SEARCHB(argsList *list.List) formulaArg {
return fn.find("SEARCHB", argsList)
}
// SUBSTITUTE function replaces one or more instances of a given text string,
// within an original text string. The syntax of the function is:
//
// SUBSTITUTE(text,old_text,new_text,[instance_num])
func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg {
if argsList.Len() != 3 && argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "SUBSTITUTE requires 3 or 4 arguments")
}
text, sourceText := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)
targetText, instanceNum := argsList.Front().Next().Next().Value.(formulaArg), 0
if argsList.Len() == 3 {
return newStringFormulaArg(strings.ReplaceAll(text.Value(), sourceText.Value(), targetText.Value()))
}
instanceNumArg := argsList.Back().Value.(formulaArg).ToNumber()
if instanceNumArg.Type != ArgNumber {
return instanceNumArg
}
instanceNum = int(instanceNumArg.Number)
if instanceNum < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "instance_num should be > 0")
}
str, sourceTextLen, count, chars, pos := text.Value(), len(sourceText.Value()), instanceNum, 0, -1
for {
count--
index := strings.Index(str, sourceText.Value())
if index == -1 {
pos = -1
break
} else {
pos = index + chars
if count == 0 {
break
}
idx := sourceTextLen + index
chars += idx
str = str[idx:]
}
}
if pos == -1 {
return newStringFormulaArg(text.Value())
}
pre, post := text.Value()[:pos], text.Value()[pos+sourceTextLen:]
return newStringFormulaArg(pre + targetText.Value() + post)
}
// TEXT function converts a supplied numeric value into text, in a
// user-specified format. The syntax of the function is:
//
// TEXT(value,format_text)
func (fn *formulaFuncs) TEXT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "TEXT requires 2 arguments")
}
value, fmtText := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg)
if value.Type == ArgError {
return value
}
if fmtText.Type == ArgError {
return fmtText
}
cellType := CellTypeNumber
if num := value.ToNumber(); num.Type != ArgNumber {
cellType = CellTypeSharedString
}
return newStringFormulaArg(format(value.Value(), fmtText.Value(), false, cellType, nil))
}
// prepareTextAfterBefore checking and prepare arguments for the formula
// functions TEXTAFTER and TEXTBEFORE.
func (fn *formulaFuncs) prepareTextAfterBefore(name string, argsList *list.List) formulaArg {
argsLen := argsList.Len()
if argsLen < 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
}
if argsLen > 6 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s accepts at most 6 arguments", name))
}
text, delimiter := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)
instanceNum, matchMode, matchEnd, ifNotFound := newNumberFormulaArg(1), newBoolFormulaArg(false), newBoolFormulaArg(false), newEmptyFormulaArg()
if argsLen > 2 {
instanceNum = argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if instanceNum.Type != ArgNumber {
return instanceNum
}
}
if argsLen > 3 {
matchMode = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool()
if matchMode.Type != ArgNumber {
return matchMode
}
if matchMode.Number == 1 {
text, delimiter = newStringFormulaArg(strings.ToLower(text.Value())), newStringFormulaArg(strings.ToLower(delimiter.Value()))
}
}
if argsLen > 4 {
matchEnd = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToBool()
if matchEnd.Type != ArgNumber {
return matchEnd
}
}
if argsLen > 5 {
ifNotFound = argsList.Back().Value.(formulaArg)
}
if text.Value() == "" {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
lenArgsList := list.New().Init()
lenArgsList.PushBack(text)
textLen := fn.LEN(lenArgsList)
if instanceNum.Number == 0 || instanceNum.Number > textLen.Number {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
reverseSearch, startPos := instanceNum.Number < 0, 0.0
if reverseSearch {
startPos = textLen.Number
}
return newListFormulaArg([]formulaArg{
text, delimiter, instanceNum, matchMode, matchEnd, ifNotFound,
textLen, newBoolFormulaArg(reverseSearch), newNumberFormulaArg(startPos),
})
}
// textAfterBeforeSearch is an implementation of the formula functions TEXTAFTER
// and TEXTBEFORE.
func textAfterBeforeSearch(text string, delimiter []string, startPos int, reverseSearch bool) (int, string) {
idx := -1
var modifiedDelimiter string
for i := 0; i < len(delimiter); i++ {
nextDelimiter := delimiter[i]
nextIdx := strings.Index(text[startPos:], nextDelimiter)
if nextIdx != -1 {
nextIdx += startPos
}
if reverseSearch {
nextIdx = strings.LastIndex(text[:startPos], nextDelimiter)
}
if idx == -1 || (((nextIdx < idx && !reverseSearch) || (nextIdx > idx && reverseSearch)) && idx != -1) {
idx = nextIdx
modifiedDelimiter = nextDelimiter
}
}
return idx, modifiedDelimiter
}
// textAfterBeforeResult is an implementation of the formula functions TEXTAFTER
// and TEXTBEFORE.
func textAfterBeforeResult(name, modifiedDelimiter string, text []rune, foundIdx, repeatZero, textLen int, matchEndActive, matchEnd, reverseSearch bool) formulaArg {
if name == "TEXTAFTER" {
endPos := len(modifiedDelimiter)
if (repeatZero > 1 || matchEndActive) && matchEnd && reverseSearch {
endPos = 0
}
if foundIdx+endPos >= textLen {
return newEmptyFormulaArg()
}
return newStringFormulaArg(string(text[foundIdx+endPos : textLen]))
}
return newStringFormulaArg(string(text[:foundIdx]))
}
// textAfterBefore is an implementation of the formula functions TEXTAFTER and
// TEXTBEFORE.
func (fn *formulaFuncs) textAfterBefore(name string, argsList *list.List) formulaArg {
args := fn.prepareTextAfterBefore(name, argsList)
if args.Type != ArgList {
return args
}
var (
text = []rune(argsList.Front().Value.(formulaArg).Value())
modifiedText = args.List[0].Value()
delimiter = []string{args.List[1].Value()}
instanceNum = args.List[2].Number
matchEnd = args.List[4].Number == 1
ifNotFound = args.List[5]
textLen = args.List[6]
reverseSearch = args.List[7].Number == 1
foundIdx = -1
repeatZero, startPos int
matchEndActive bool
modifiedDelimiter string
)
if reverseSearch {
startPos = int(args.List[8].Number)
}
for i := 0; i < int(math.Abs(instanceNum)); i++ {
foundIdx, modifiedDelimiter = textAfterBeforeSearch(modifiedText, delimiter, startPos, reverseSearch)
if foundIdx == 0 {
repeatZero++
}
if foundIdx == -1 {
if matchEnd && i == int(math.Abs(instanceNum))-1 {
if foundIdx = int(textLen.Number); reverseSearch {
foundIdx = 0
}
matchEndActive = true
}
break
}
if startPos = foundIdx + len(modifiedDelimiter); reverseSearch {
startPos = foundIdx - len(modifiedDelimiter)
}
}
if foundIdx == -1 {
return ifNotFound
}
return textAfterBeforeResult(name, modifiedDelimiter, text, foundIdx, repeatZero, int(textLen.Number), matchEndActive, matchEnd, reverseSearch)
}
// TEXTAFTER function returns the text that occurs after a given substring or
// delimiter. The syntax of the function is:
//
// TEXTAFTER(text,delimiter,[instance_num],[match_mode],[match_end],[if_not_found])
func (fn *formulaFuncs) TEXTAFTER(argsList *list.List) formulaArg {
return fn.textAfterBefore("TEXTAFTER", argsList)
}
// TEXTBEFORE function returns text that occurs before a given character or
// string. The syntax of the function is:
//
// TEXTBEFORE(text,delimiter,[instance_num],[match_mode],[match_end],[if_not_found])
func (fn *formulaFuncs) TEXTBEFORE(argsList *list.List) formulaArg {
return fn.textAfterBefore("TEXTBEFORE", argsList)
}
// TEXTJOIN function joins together a series of supplied text strings into one
// combined text string. The user can specify a delimiter to add between the
// individual text items, if required. The syntax of the function is:
//
// TEXTJOIN([delimiter],[ignore_empty],text1,[text2],...)
func (fn *formulaFuncs) TEXTJOIN(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN requires at least 3 arguments")
}
if argsList.Len() > 252 {
return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN accepts at most 252 arguments")
}
delimiter := argsList.Front().Value.(formulaArg)
ignoreEmpty := argsList.Front().Next().Value.(formulaArg)
if ignoreEmpty.Type != ArgNumber || !ignoreEmpty.Boolean {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args, ok := textJoin(argsList.Front().Next().Next(), []string{}, ignoreEmpty.Number != 0)
if ok.Type != ArgNumber {
return ok
}
result := strings.Join(args, delimiter.Value())
if countUTF16String(result) > TotalCellChars {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("TEXTJOIN function exceeds %d characters", TotalCellChars))
}
return newStringFormulaArg(result)
}
// textJoin is an implementation of the formula function TEXTJOIN.
func textJoin(arg *list.Element, arr []string, ignoreEmpty bool) ([]string, formulaArg) {
for arg.Next(); arg != nil; arg = arg.Next() {
switch arg.Value.(formulaArg).Type {
case ArgError:
return arr, arg.Value.(formulaArg)
case ArgString, ArgEmpty:
val := arg.Value.(formulaArg).Value()
if val != "" || !ignoreEmpty {
arr = append(arr, val)
}
case ArgNumber:
arr = append(arr, arg.Value.(formulaArg).Value())
case ArgMatrix:
for _, row := range arg.Value.(formulaArg).Matrix {
argList := list.New().Init()
for _, ele := range row {
argList.PushBack(ele)
}
if argList.Len() > 0 {
args, _ := textJoin(argList.Front(), []string{}, ignoreEmpty)
arr = append(arr, args...)
}
}
}
}
return arr, newBoolFormulaArg(true)
}
// TRIM removes extra spaces (i.e. all spaces except for single spaces between
// words or characters) from a supplied text string. The syntax of the
// function is:
//
// TRIM(text)
func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
}
return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).Value()))
}
// UNICHAR returns the Unicode character that is referenced by the given
// numeric value. The syntax of the function is:
//
// UNICHAR(number)
func (fn *formulaFuncs) UNICHAR(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "UNICHAR requires 1 argument")
}
numArg := argsList.Front().Value.(formulaArg).ToNumber()
if numArg.Type != ArgNumber {
return numArg
}
if numArg.Number <= 0 || numArg.Number > 55295 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newStringFormulaArg(string(rune(numArg.Number)))
}
// UNICODE function returns the code point for the first character of a
// supplied text string. The syntax of the function is:
//
// UNICODE(text)
func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg {
return fn.code("UNICODE", argsList)
}
// UNIQUE function returns a list of unique values in a list or range. The
// syntax of the function is:
//
// UNIQUE(array,[by_col],[exactly_once])
func (fn *formulaFuncs) UNIQUE(argsList *list.List) formulaArg {
args, errArg := getFormulaUniqueArgs(argsList)
if errArg != nil {
return *errArg
}
if args.byColumn {
args.cellRange, args.cols, args.rows = transposeFormulaArgsList(args.cellRange, args.cols, args.rows)
}
counts := map[string]int{}
for i := 0; i < len(args.cellRange); i += args.cols {
key := concatValues(args.cellRange[i : i+args.cols])
if _, ok := counts[key]; !ok {
counts[key] = 0
}
counts[key]++
}
uniqueAxes := [][]formulaArg{}
for i := 0; i < len(args.cellRange); i += args.cols {
key := concatValues(args.cellRange[i : i+args.cols])
if (args.exactlyOnce && counts[key] == 1) || (!args.exactlyOnce && counts[key] >= 1) {
uniqueAxes = append(uniqueAxes, args.cellRange[i:i+args.cols])
}
delete(counts, key)
}
if args.byColumn {
uniqueAxes = transposeFormulaArgsMatrix(uniqueAxes)
}
return newMatrixFormulaArg(uniqueAxes)
}
// transposeFormulaArgsMatrix transposes a 2D slice of formulaArg.
func transposeFormulaArgsMatrix(args [][]formulaArg) [][]formulaArg {
if len(args) == 0 {
return args
}
transposedArgs := make([][]formulaArg, len(args[0]))
for i := 0; i < len(args[0]); i++ {
transposedArgs[i] = make([]formulaArg, len(args))
}
for i := 0; i < len(args); i++ {
for j := 0; j < len(args[i]); j++ {
transposedArgs[j][i] = args[i][j]
}
}
return transposedArgs
}
// transposeFormulaArgsList transposes a flat slice of formulaArg given the
// number of columns and rows.
func transposeFormulaArgsList(args []formulaArg, cols, rows int) ([]formulaArg, int, int) {
transposedArgs := make([]formulaArg, len(args))
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
transposedArgs[j*rows+i] = args[i*cols+j]
}
}
return transposedArgs, rows, cols
}
// concatValues concatenates the values of a slice of formulaArg into a single
// string.
func concatValues(args []formulaArg) string {
val := ""
for _, arg := range args {
// Call to Value is cheap.
val += arg.Value()
}
return val
}
// uniqueArgs holds the parsed arguments for the UNIQUE function.
type uniqueArgs struct {
cellRange []formulaArg
cols int
rows int
byColumn bool
exactlyOnce bool
}
// getFormulaUniqueArgs parses and validates the arguments for the UNIQUE
// function.
func getFormulaUniqueArgs(argsList *list.List) (uniqueArgs, *formulaArg) {
res := uniqueArgs{}
argsLen := argsList.Len()
if argsLen == 0 {
errArg := newErrorFormulaArg(formulaErrorVALUE, "UNIQUE requires at least 1 argument")
return res, &errArg
}
if argsLen > 3 {
msg := fmt.Sprintf("UNIQUE takes at most 3 arguments, received %d arguments", argsLen)
errArg := newErrorFormulaArg(formulaErrorVALUE, msg)
return res, &errArg
}
firstArg := argsList.Front()
res.cellRange = firstArg.Value.(formulaArg).ToList()
if len(res.cellRange) == 0 {
errArg := newErrorFormulaArg(formulaErrorVALUE, "missing first argument to UNIQUE")
return res, &errArg
}
if res.cellRange[0].Type == ArgError {
return res, &res.cellRange[0]
}
rmin, rmax := calcColsRowsMinMax(false, argsList)
cmin, cmax := calcColsRowsMinMax(true, argsList)
res.cols, res.rows = cmax-cmin+1, rmax-rmin+1
secondArg := firstArg.Next()
if secondArg == nil {
return res, nil
}
argByColumn := secondArg.Value.(formulaArg).ToBool()
if argByColumn.Type == ArgError {
return res, &argByColumn
}
res.byColumn = (argByColumn.Value() == "TRUE")
thirdArg := secondArg.Next()
if thirdArg == nil {
return res, nil
}
argExactlyOnce := thirdArg.Value.(formulaArg).ToBool()
if argExactlyOnce.Type == ArgError {
return res, &argExactlyOnce
}
res.exactlyOnce = (argExactlyOnce.Value() == "TRUE")
return res, nil
}
// UPPER converts all characters in a supplied text string to upper case. The
// syntax of the function is:
//
// UPPER(text)
func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument")
}
return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).Value()))
}
// VALUE function converts a text string into a numeric value. The syntax of
// the function is:
//
// VALUE(text)
func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "VALUE requires 1 argument")
}
text := strings.ReplaceAll(argsList.Front().Value.(formulaArg).Value(), ",", "")
percent := 1.0
if strings.HasSuffix(text, "%") {
percent, text = 0.01, strings.TrimSuffix(text, "%")
}
decimal := big.Float{}
if _, ok := decimal.SetString(text); ok {
value, _ := decimal.Float64()
return newNumberFormulaArg(value * percent)
}
dateValue, timeValue, errTime := 0.0, 0.0, false
if !isDateOnlyFmt(text) {
h, m, s, _, _, err := strToTime(text)
errTime = err.Type == ArgError
if !errTime {
timeValue = (float64(h)*3600 + float64(m)*60 + s) / 86400
}
}
y, m, d, _, err := strToDate(text)
errDate := err.Type == ArgError
if !errDate {
dateValue = daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1
}
if errTime && errDate {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newNumberFormulaArg(dateValue + timeValue)
}
// VALUETOTEXT function returns text from any specified value. It passes text
// values unchanged, and converts non-text values to text.
//
// VALUETOTEXT(value,[format])
func (fn *formulaFuncs) VALUETOTEXT(argsList *list.List) formulaArg {
format := prepareToText("VALUETOTEXT", argsList)
if format.Type != ArgNumber {
return format
}
cell := argsList.Front().Value.(formulaArg)
if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 {
return newStringFormulaArg(fmt.Sprintf("\"%s\"", cell.Value()))
}
return newStringFormulaArg(cell.Value())
}
// Conditional Functions
// IF function tests a supplied condition and returns one result if the
// condition evaluates to TRUE, and another result if the condition evaluates
// to FALSE. The syntax of the function is:
//
// IF(logical_test,value_if_true,value_if_false)
func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "IF requires at least 1 argument")
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "IF accepts at most 3 arguments")
}
token := fn.implicitIntersect(argsList.Front().Value.(formulaArg))
var (
cond bool
err error
result formulaArg
)
switch token.Type {
case ArgString:
if cond, err = strconv.ParseBool(token.Value()); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
cond = token.Number == 1
}
if argsList.Len() == 1 {
return newBoolFormulaArg(cond)
}
if cond {
value := fn.implicitIntersect(argsList.Front().Next().Value.(formulaArg))
switch value.Type {
case ArgNumber:
result = value.ToNumber()
default:
result = newStringFormulaArg(value.Value())
}
return result
}
if argsList.Len() == 3 {
value := fn.implicitIntersect(argsList.Back().Value.(formulaArg))
switch value.Type {
case ArgNumber:
result = value.ToNumber()
default:
result = newStringFormulaArg(value.Value())
}
}
return result
}
// Lookup and Reference Functions
// ADDRESS function takes a row and a column number and returns a cell
// reference as a text string. The syntax of the function is:
//
// ADDRESS(row_num,column_num,[abs_num],[a1],[sheet_text])
func (fn *formulaFuncs) ADDRESS(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at least 2 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at most 5 arguments")
}
rowNum := argsList.Front().Value.(formulaArg).ToNumber()
if rowNum.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if rowNum.Number > TotalRows {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
colNum := argsList.Front().Next().Value.(formulaArg).ToNumber()
if colNum.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
absNum := newNumberFormulaArg(1)
if argsList.Len() >= 3 {
absNum = argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if absNum.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
if absNum.Number < 1 || absNum.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
a1 := newBoolFormulaArg(true)
if argsList.Len() >= 4 {
a1 = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool()
if a1.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
var sheetText string
if argsList.Len() == 5 {
sheetText = fmt.Sprintf("%s!", argsList.Back().Value.(formulaArg).Value())
}
formatter := addressFmtMaps[fmt.Sprintf("%d_%s", int(absNum.Number), a1.Value())]
addr, err := formatter(int(colNum.Number), int(rowNum.Number))
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newStringFormulaArg(fmt.Sprintf("%s%s", sheetText, addr))
}
// ANCHORARRAY function returns the entire spilled range for the dynamic array
// in cell. The syntax of the function is:
//
// ANCHORARRAY(cell)
func (fn *formulaFuncs) ANCHORARRAY(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ANCHORARRAY requires 1 numeric argument")
}
ws, err := fn.f.workSheetReader(fn.sheet)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
ref := argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef)
cell := ws.SheetData.Row[ref.Row-1].C[ref.Col-1]
if cell.F == nil {
return newEmptyFormulaArg()
}
coordinates, err := rangeRefToCoordinates(cell.F.Ref)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
_ = sortCoordinates(coordinates)
var mtx [][]formulaArg
for c := coordinates[0]; c <= coordinates[2]; c++ {
var row []formulaArg
for r := coordinates[1]; r <= coordinates[3]; r++ {
cellName, _ := CoordinatesToCellName(c, r)
result, err := fn.f.CalcCellValue(ref.Sheet, cellName, Options{RawCellValue: true})
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
arg := newStringFormulaArg(result)
if num := arg.ToNumber(); num.Type == ArgNumber {
arg = num
}
row = append(row, arg)
}
mtx = append(mtx, row)
}
return newMatrixFormulaArg(mtx)
}
// CHOOSE function returns a value from an array, that corresponds to a
// supplied index number (position). The syntax of the function is:
//
// CHOOSE(index_num,value1,[value2],...)
func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments")
}
idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).Value())
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number")
}
if argsList.Len() <= idx {
return newErrorFormulaArg(formulaErrorVALUE, "index_num should be <= to the number of values")
}
arg := argsList.Front()
for i := 0; i < idx; i++ {
arg = arg.Next()
}
return arg.Value.(formulaArg)
}
// matchPatternToRegExp convert find text pattern to regular expression.
func matchPatternToRegExp(findText string, dbcs bool) (string, bool) {
var (
exp string
wildCard bool
mark = "."
)
if dbcs {
mark = "(?:(?:[\\x00-\\x0081])|(?:[\\xFF61-\\xFFA0])|(?:[\\xF8F1-\\xF8F4])|[0-9A-Za-z])"
}
for _, char := range findText {
if strings.ContainsAny(string(char), ".+$^[](){}|/") {
exp += fmt.Sprintf("\\%s", string(char))
continue
}
if char == '?' {
wildCard = true
exp += mark
continue
}
if char == '*' {
wildCard = true
exp += ".*"
continue
}
exp += string(char)
}
return fmt.Sprintf("^%s", exp), wildCard
}
// matchPattern finds whether the text matches or satisfies the pattern
// string. The pattern supports '*' and '?' wildcards in the pattern string.
func matchPattern(findText, withinText string, dbcs bool, startNum int) (int, bool) {
exp, wildCard := matchPatternToRegExp(findText, dbcs)
offset := 1
for idx := range withinText {
if offset < startNum {
offset++
continue
}
if wildCard {
if ok, _ := regexp.MatchString(exp, withinText[idx:]); ok {
break
}
}
if strings.Index(withinText[idx:], findText) == 0 {
break
}
offset++
}
return offset, countUTF16String(withinText) != offset-1
}
// compareFormulaArg compares the left-hand sides and the right-hand sides'
// formula arguments by given conditions such as case-sensitive, if exact
// match, and make compare result as formula criteria condition type.
func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
if lhs.Type != rhs.Type {
return criteriaNe
}
switch lhs.Type {
case ArgNumber:
if lhs.Number == rhs.Number {
return criteriaEq
}
if lhs.Number < rhs.Number {
return criteriaL
}
return criteriaG
case ArgString:
ls, rs := lhs.Value(), rhs.Value()
if !caseSensitive {
ls, rs = strings.ToLower(ls), strings.ToLower(rs)
}
if matchMode.Number == matchModeWildcard {
if _, ok := matchPattern(rs, ls, false, 0); ok {
return criteriaEq
}
}
return map[int]byte{1: criteriaG, -1: criteriaL, 0: criteriaEq}[strings.Compare(ls, rs)]
case ArgEmpty:
return criteriaEq
case ArgList:
return compareFormulaArgList(lhs, rhs, matchMode, caseSensitive)
case ArgMatrix:
return compareFormulaArgMatrix(lhs, rhs, matchMode, caseSensitive)
default:
return criteriaErr
}
}
// compareFormulaArgList compares the left-hand sides and the right-hand sides
// list type formula arguments.
func compareFormulaArgList(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
if len(lhs.List) < len(rhs.List) {
return criteriaL
}
if len(lhs.List) > len(rhs.List) {
return criteriaG
}
for arg := range lhs.List {
criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], matchMode, caseSensitive)
if criteria != criteriaEq {
return criteria
}
}
return criteriaEq
}
// compareFormulaArgMatrix compares the left-hand sides and the right-hand sides'
// matrix type formula arguments.
func compareFormulaArgMatrix(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
if len(lhs.Matrix) < len(rhs.Matrix) {
return criteriaL
}
if len(lhs.Matrix) > len(rhs.Matrix) {
return criteriaG
}
for i := range lhs.Matrix {
left, right := lhs.Matrix[i], rhs.Matrix[i]
if len(left) < len(right) {
return criteriaL
}
if len(left) > len(right) {
return criteriaG
}
for arg := range left {
criteria := compareFormulaArg(left[arg], right[arg], matchMode, caseSensitive)
if criteria != criteriaEq {
return criteria
}
}
}
return criteriaEq
}
// COLUMN function returns the first column number within a supplied reference
// or the number of the current column. The syntax of the function is:
//
// COLUMN([reference])
func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg {
if argsList.Len() > 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COLUMN requires at most 1 argument")
}
if argsList.Len() == 1 {
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Col))
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Col))
}
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
}
col, _, _ := CellNameToCoordinates(fn.cell)
return newNumberFormulaArg(float64(col))
}
// calcColsRowsMinMax calculation min and max value for given formula arguments
// sequence of the formula functions COLUMNS and ROWS.
func calcColsRowsMinMax(cols bool, argsList *list.List) (minVal, maxVal int) {
getVal := func(cols bool, cell cellRef) int {
if cols {
return cell.Col
}
return cell.Row
}
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
crs := argsList.Front().Value.(formulaArg).cellRanges
for cr := crs.Front(); cr != nil; cr = cr.Next() {
if minVal == 0 {
minVal = getVal(cols, cr.Value.(cellRange).From)
}
if maxVal < getVal(cols, cr.Value.(cellRange).To) {
maxVal = getVal(cols, cr.Value.(cellRange).To)
}
}
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
cr := argsList.Front().Value.(formulaArg).cellRefs
for refs := cr.Front(); refs != nil; refs = refs.Next() {
if minVal == 0 {
minVal = getVal(cols, refs.Value.(cellRef))
}
if maxVal < getVal(cols, refs.Value.(cellRef)) {
maxVal = getVal(cols, refs.Value.(cellRef))
}
}
}
return
}
// COLUMNS function receives an Excel range and returns the number of columns
// that are contained within the range. The syntax of the function is:
//
// COLUMNS(array)
func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
}
minVal, maxVal := calcColsRowsMinMax(true, argsList)
if maxVal == MaxColumns {
return newNumberFormulaArg(float64(MaxColumns))
}
result := maxVal - minVal + 1
if maxVal == minVal {
if minVal == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
}
return newNumberFormulaArg(float64(1))
}
return newNumberFormulaArg(float64(result))
}
// FORMULATEXT function returns a formula as a text string. The syntax of the
// function is:
//
// FORMULATEXT(reference)
func (fn *formulaFuncs) FORMULATEXT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "FORMULATEXT requires 1 argument")
}
refs := argsList.Front().Value.(formulaArg).cellRefs
col, row := 0, 0
if refs != nil && refs.Len() > 0 {
col, row = refs.Front().Value.(cellRef).Col, refs.Front().Value.(cellRef).Row
}
ranges := argsList.Front().Value.(formulaArg).cellRanges
if ranges != nil && ranges.Len() > 0 {
col, row = ranges.Front().Value.(cellRange).From.Col, ranges.Front().Value.(cellRange).From.Row
}
cell, err := CoordinatesToCellName(col, row)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
formula, _ := fn.f.GetCellFormula(fn.sheet, cell)
return newStringFormulaArg(formula)
}
// checkHVLookupArgs checking arguments, prepare extract mode, lookup value,
// and data for the formula functions HLOOKUP and VLOOKUP.
func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue, tableArray, matchMode, errArg formulaArg) {
unit := map[string]string{
"HLOOKUP": "row",
"VLOOKUP": "col",
}[name]
if argsList.Len() < 3 {
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name))
return
}
if argsList.Len() > 4 {
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 4 arguments", name))
return
}
lookupValue = argsList.Front().Value.(formulaArg)
tableArray = argsList.Front().Next().Value.(formulaArg)
if tableArray.Type != ArgMatrix {
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires second argument of table array", name))
return
}
arg := argsList.Front().Next().Next().Value.(formulaArg)
if arg.Type != ArgNumber || arg.Boolean {
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit))
return
}
idx, matchMode = int(arg.Number)-1, newNumberFormulaArg(matchModeMaxLess)
if argsList.Len() == 4 {
rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
if rangeLookup.Type == ArgError {
errArg = rangeLookup
return
}
if rangeLookup.Number == 0 {
matchMode = newNumberFormulaArg(matchModeWildcard)
}
}
return
}
// HLOOKUP function 'looks up' a given value in the top row of a data array
// (or table), and returns the corresponding value from another row of the
// array. The syntax of the function is:
//
// HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup])
func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
rowIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("HLOOKUP", argsList)
if errArg.Type == ArgError {
return errArg
}
var matchIdx int
var wasExact bool
if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows {
matchIdx, wasExact = lookupLinearSearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear))
} else {
matchIdx, wasExact = lookupBinarySearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary))
}
if matchIdx == -1 {
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
}
if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) {
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index")
}
row := tableArray.Matrix[rowIdx]
if wasExact || matchMode.Number == matchModeWildcard {
return row[matchIdx]
}
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
}
// HYPERLINK function creates a hyperlink to a specified location. The syntax
// of the function is:
//
// HYPERLINK(link_location,[friendly_name])
func (fn *formulaFuncs) HYPERLINK(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "HYPERLINK requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "HYPERLINK allows at most 2 arguments")
}
return newStringFormulaArg(argsList.Back().Value.(formulaArg).Value())
}
// calcMatch returns the position of the value by given match type, criteria
// and lookup array for the formula function MATCH.
// matchType only contains -1, and 1.
func calcMatchMatrix(vertical bool, matchType int, criteria *formulaCriteria, lookupArray [][]formulaArg) formulaArg {
idx := -1
calc := func(i int, arg formulaArg) bool {
switch matchType {
case -1:
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaGe, Condition: criteria.Condition,
}); ok {
idx = i
return false
}
if criteria.Condition.Type == ArgNumber {
return true
}
case 1:
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaLe, Condition: criteria.Condition,
}); ok {
idx = i
return false
}
if criteria.Condition.Type == ArgNumber {
return true
}
}
return false
}
if vertical {
for i, row := range lookupArray {
if ok := calc(i, row[0]); ok {
break
}
}
} else {
for i, cell := range lookupArray[0] {
if ok := calc(i, cell); ok {
break
}
}
}
if idx == -1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(float64(idx + 1))
}
// calcMatch returns the position of the value by given match type, criteria
// and lookup array for the formula function MATCH.
func calcMatch(matchType int, criteria *formulaCriteria, lookupArray []formulaArg) formulaArg {
idx := -1
switch matchType {
case 0:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg, criteria); ok {
return newNumberFormulaArg(float64(i + 1))
}
}
case -1:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaGe, Condition: criteria.Condition,
}); ok {
idx = i
continue
}
if criteria.Condition.Type == ArgNumber {
break
}
}
case 1:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaLe, Condition: criteria.Condition,
}); ok {
idx = i
continue
}
if criteria.Condition.Type == ArgNumber {
break
}
}
}
if idx == -1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(float64(idx + 1))
}
// MATCH function looks up a value in an array, and returns the position of
// the value within the array. The user can specify that the function should
// only return a result if an exact match is found, or that the function
// should return the position of the closest match (above or below), if an
// exact match is not found. The syntax of the Match function is:
//
// MATCH(lookup_value,lookup_array,[match_type])
func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg {
if argsList.Len() != 2 && argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires 1 or 2 arguments")
}
var (
matchType = 1
lookupArray []formulaArg
lookupArrayArg = argsList.Front().Next().Value.(formulaArg)
lookupArrayErr = "MATCH arguments lookup_array should be one-dimensional array"
)
if argsList.Len() == 3 {
matchTypeArg := argsList.Back().Value.(formulaArg).ToNumber()
if matchTypeArg.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires numeric match_type argument")
}
if matchTypeArg.Number == -1 || matchTypeArg.Number == 0 {
matchType = int(matchTypeArg.Number)
}
}
switch lookupArrayArg.Type {
case ArgMatrix:
if len(lookupArrayArg.Matrix) != 1 && len(lookupArrayArg.Matrix[0]) != 1 {
return newErrorFormulaArg(formulaErrorNA, lookupArrayErr)
}
lookupArray = lookupArrayArg.ToList()
default:
return newErrorFormulaArg(formulaErrorNA, lookupArrayErr)
}
return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg)), lookupArray)
}
// TRANSPOSE function 'transposes' an array of cells (i.e. the function copies
// a horizontal range of cells into a vertical range and vice versa). The
// syntax of the function is:
//
// TRANSPOSE(array)
func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument")
}
args := argsList.Back().Value.(formulaArg).ToList()
rmin, rmax := calcColsRowsMinMax(false, argsList)
cmin, cmax := calcColsRowsMinMax(true, argsList)
cols, rows := cmax-cmin+1, rmax-rmin+1
src := make([][]formulaArg, 0)
for i := 0; i < len(args); i += cols {
src = append(src, args[i:i+cols])
}
mtx := make([][]formulaArg, cols)
for r, row := range src {
colIdx := r % rows
for c, cell := range row {
rowIdx := c % cols
if len(mtx[rowIdx]) == 0 {
mtx[rowIdx] = make([]formulaArg, rows)
}
mtx[rowIdx][colIdx] = cell
}
}
return newMatrixFormulaArg(mtx)
}
// lookupLinearSearch sequentially checks each look value of the lookup array until
// a match is found or the whole list has been searched.
func lookupLinearSearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (int, bool) {
matchIdx, wasExact := -1, false
linearSearch := func(i int, cell, lhs formulaArg) bool {
if lookupValue.Type == ArgNumber {
if lhs = cell.ToNumber(); lhs.Type == ArgError {
lhs = cell
}
} else if lookupValue.Type == ArgMatrix {
lhs = lookupArray
} else if lookupArray.Type == ArgString {
lhs = newStringFormulaArg(cell.Value())
}
if compareFormulaArg(lhs, lookupValue, matchMode, false) == criteriaEq {
matchIdx = i
wasExact = true
if searchMode.Number == searchModeLinear {
return true
}
}
if matchMode.Number == matchModeMinGreater || matchMode.Number == matchModeMaxLess {
matchIdx = int(calcMatchMatrix(vertical, int(matchMode.Number), formulaCriteriaParser(lookupValue), lookupArray.Matrix).Number)
return false
}
return false
}
if vertical {
for i, row := range lookupArray.Matrix {
lhs := row[0]
if linearSearch(i, lhs, lhs) {
break
}
}
} else {
for i, lhs := range lookupArray.Matrix[0] {
if linearSearch(i, lhs, lhs) {
break
}
}
}
return matchIdx, wasExact
}
// VLOOKUP function 'looks up' a given value in the left-hand column of a
// data array (or table), and returns the corresponding value from another
// column of the array. The syntax of the function is:
//
// VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup])
func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
colIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("VLOOKUP", argsList)
if errArg.Type == ArgError {
return errArg
}
var matchIdx int
var wasExact bool
if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows {
matchIdx, wasExact = lookupLinearSearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear))
} else {
matchIdx, wasExact = lookupBinarySearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary))
}
if matchIdx == -1 {
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
}
mtx := tableArray.Matrix[matchIdx]
if colIdx < 0 || colIdx >= len(mtx) {
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index")
}
if wasExact || matchMode.Number == matchModeWildcard {
return mtx[colIdx]
}
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
}
// lookupBinarySearch finds the position of a target value when range lookup
// is TRUE, if the data of table array can't guarantee be sorted, it will
// return wrong result.
func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (matchIdx int, wasExact bool) {
var tableArray []formulaArg
if vertical {
for _, row := range lookupArray.Matrix {
tableArray = append(tableArray, row[0])
}
} else {
tableArray = lookupArray.Matrix[0]
}
low, high, lastMatchIdx := 0, len(tableArray)-1, -1
count := high
for low <= high {
mid := low + (high-low)/2
cell := tableArray[mid]
lhs := cell
if lookupValue.Type == ArgNumber {
if lhs = cell.ToNumber(); lhs.Type == ArgError {
lhs = cell
}
} else if lookupValue.Type == ArgMatrix && vertical {
lhs = lookupArray
} else if lookupValue.Type == ArgString {
lhs = newStringFormulaArg(cell.Value())
}
result := compareFormulaArg(lhs, lookupValue, matchMode, false)
switch result {
case criteriaEq:
matchIdx, wasExact = mid, true
if searchMode.Number == searchModeDescBinary {
matchIdx = count - matchIdx
}
return
case criteriaG:
high = mid - 1
case criteriaL:
matchIdx = mid
if cell.Type != ArgEmpty {
lastMatchIdx = matchIdx
}
low = mid + 1
default:
return -1, false
}
}
matchIdx, wasExact = lastMatchIdx, true
return
}
// checkLookupArgs checking arguments, prepare lookup value, and data for the
// formula function LOOKUP.
func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVector, errArg formulaArg) {
if argsList.Len() < 2 {
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments")
return
}
if argsList.Len() > 3 {
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
return
}
lookupValue = newStringFormulaArg(argsList.Front().Value.(formulaArg).Value())
lookupVector = argsList.Front().Next().Value.(formulaArg)
if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList {
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
return
}
arrayForm = lookupVector.Type == ArgMatrix
if arrayForm && len(lookupVector.Matrix) == 0 {
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires not empty range as second argument")
}
return
}
// iterateLookupArgs iterate arguments to extract columns and calculate match
// index for the formula function LOOKUP.
func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int, bool) {
cols, matchIdx, ok := lookupCol(lookupVector, 0), -1, false
for idx, col := range cols {
lhs := lookupValue
switch col.Type {
case ArgNumber:
lhs = lhs.ToNumber()
if !col.Boolean {
if lhs.Type == ArgError {
lhs = lookupValue
}
}
}
compare := compareFormulaArg(lhs, col, newNumberFormulaArg(matchModeMaxLess), false)
// Find exact match
if compare == criteriaEq {
matchIdx = idx
break
}
// Find the nearest match if lookup value is more than or equal to the first value in lookup vector
if idx == 0 {
ok = compare == criteriaG
} else if ok && compare == criteriaL && matchIdx == -1 {
matchIdx = idx - 1
}
}
return cols, matchIdx, ok
}
// index is an implementation of the formula function INDEX.
func (fn *formulaFuncs) index(array formulaArg, rowIdx, colIdx int) formulaArg {
var cells []formulaArg
if array.Type == ArgMatrix {
cellMatrix := array.Matrix
if rowIdx < -1 || rowIdx >= len(cellMatrix) {
return newErrorFormulaArg(formulaErrorREF, "INDEX row_num out of range")
}
if rowIdx == -1 {
if colIdx >= len(cellMatrix[0]) {
return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range")
}
var column [][]formulaArg
for _, cells = range cellMatrix {
column = append(column, []formulaArg{cells[colIdx]})
}
return newMatrixFormulaArg(column)
}
cells = cellMatrix[rowIdx]
}
if colIdx < -1 || colIdx >= len(cells) {
return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range")
}
return newListFormulaArg(cells)
}
// validateMatchMode check the number of match mode if be equal to 0, 1, -1 or
// 2.
func validateMatchMode(mode float64) bool {
return mode == matchModeExact || mode == matchModeMinGreater || mode == matchModeMaxLess || mode == matchModeWildcard
}
// validateSearchMode check the number of search mode if be equal to 1, -1, 2
// or -2.
func validateSearchMode(mode float64) bool {
return mode == searchModeLinear || mode == searchModeReverseLinear || mode == searchModeAscBinary || mode == searchModeDescBinary
}
// prepareXlookupArgs checking and prepare arguments for the formula function
// XLOOKUP.
func (fn *formulaFuncs) prepareXlookupArgs(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP requires at least 3 arguments")
}
if argsList.Len() > 6 {
return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP allows at most 6 arguments")
}
lookupValue := argsList.Front().Value.(formulaArg)
lookupArray := argsList.Front().Next().Value.(formulaArg)
returnArray := argsList.Front().Next().Next().Value.(formulaArg)
ifNotFond := newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
matchMode, searchMode := newNumberFormulaArg(matchModeExact), newNumberFormulaArg(searchModeLinear)
if argsList.Len() > 3 {
ifNotFond = argsList.Front().Next().Next().Next().Value.(formulaArg)
}
if argsList.Len() > 4 {
if matchMode = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); matchMode.Type != ArgNumber {
return matchMode
}
}
if argsList.Len() > 5 {
if searchMode = argsList.Back().Value.(formulaArg).ToNumber(); searchMode.Type != ArgNumber {
return searchMode
}
}
if lookupArray.Type != ArgMatrix || returnArray.Type != ArgMatrix {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if !validateMatchMode(matchMode.Number) || !validateSearchMode(searchMode.Number) {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newListFormulaArg([]formulaArg{lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode})
}
// xlookup is an implementation of the formula function XLOOKUP.
func (fn *formulaFuncs) xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx int,
condition1, condition2, condition3, condition4 bool, returnArray formulaArg,
) formulaArg {
var result [][]formulaArg
for rowIdx, row := range returnArray.Matrix {
for colIdx, cell := range row {
if condition1 {
if condition2 {
result = append(result, []formulaArg{cell})
continue
}
if returnArrayRows > 1 && returnArrayCols > 1 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
if condition3 {
if returnArrayCols != lookupCols {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if colIdx == matchIdx {
result = append(result, []formulaArg{cell})
continue
}
}
if condition4 {
if returnArrayRows != lookupRows {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if rowIdx == matchIdx {
if len(result) == 0 {
result = append(result, []formulaArg{cell})
continue
}
result[0] = append(result[0], cell)
}
}
}
}
array := newMatrixFormulaArg(result)
cells := array.ToList()
if len(cells) == 1 {
return cells[0]
}
return array
}
// XLOOKUP function searches a range or an array, and then returns the item
// corresponding to the first match it finds. If no match exists, then
// XLOOKUP can return the closest (approximate) match. The syntax of the
// function is:
//
// XLOOKUP(lookup_value,lookup_array,return_array,[if_not_found],[match_mode],[search_mode])
func (fn *formulaFuncs) XLOOKUP(argsList *list.List) formulaArg {
args := fn.prepareXlookupArgs(argsList)
if args.Type != ArgList {
return args
}
lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]
lookupRows, lookupCols := len(lookupArray.Matrix), 0
if lookupRows > 0 {
lookupCols = len(lookupArray.Matrix[0])
}
if lookupRows != 1 && lookupCols != 1 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
verticalLookup := lookupRows >= lookupCols
var matchIdx int
switch searchMode.Number {
case searchModeLinear, searchModeReverseLinear:
matchIdx, _ = lookupLinearSearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode)
default:
matchIdx, _ = lookupBinarySearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode)
}
if matchIdx == -1 {
return ifNotFond
}
returnArrayRows, returnArrayCols := len(returnArray.Matrix), len(returnArray.Matrix[0])
condition1 := lookupRows == 1 && lookupCols == 1
condition2 := returnArrayRows == 1 || returnArrayCols == 1
condition3 := lookupRows == 1 && lookupCols > 1
condition4 := lookupRows > 1 && lookupCols == 1
return fn.xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx, condition1, condition2, condition3, condition4, returnArray)
}
// INDEX function returns a reference to a cell that lies in a specified row
// and column of a range of cells. The syntax of the function is:
//
// INDEX(array,row_num,[col_num])
func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg {
if argsList.Len() < 2 || argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "INDEX requires 2 or 3 arguments")
}
array := argsList.Front().Value.(formulaArg)
if array.Type != ArgMatrix && array.Type != ArgList {
array = newMatrixFormulaArg([][]formulaArg{{array}})
}
rowArg := argsList.Front().Next().Value.(formulaArg).ToNumber()
if rowArg.Type != ArgNumber {
return rowArg
}
rowIdx, colIdx := int(rowArg.Number)-1, -1
if argsList.Len() == 3 {
colArg := argsList.Back().Value.(formulaArg).ToNumber()
if colArg.Type != ArgNumber {
return colArg
}
colIdx = int(colArg.Number) - 1
}
if rowIdx == -1 && colIdx == -1 {
if len(array.ToList()) != 1 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return array.ToList()[0]
}
cells := fn.index(array, rowIdx, colIdx)
if cells.Type != ArgList {
return cells
}
if colIdx == -1 {
return newMatrixFormulaArg([][]formulaArg{cells.List})
}
return cells.List[colIdx]
}
// INDIRECT function converts a text string into a cell reference. The syntax
// of the Indirect function is:
//
// INDIRECT(ref_text,[a1])
func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg {
if argsList.Len() != 1 && argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "INDIRECT requires 1 or 2 arguments")
}
refText := argsList.Front().Value.(formulaArg).Value()
a1 := newBoolFormulaArg(true)
if argsList.Len() == 2 {
if a1 = argsList.Back().Value.(formulaArg).ToBool(); a1.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
R1C1ToA1 := func(ref string) (cell string, err error) {
parts := strings.Split(strings.TrimLeft(ref, "R"), "C")
if len(parts) != 2 {
return
}
row, err := strconv.Atoi(parts[0])
if err != nil {
return
}
col, err := strconv.Atoi(parts[1])
if err != nil {
return
}
cell, err = CoordinatesToCellName(col, row)
return
}
refs := strings.Split(refText, ":")
fromRef, toRef := refs[0], ""
if len(refs) == 2 {
toRef = refs[1]
}
if a1.Number == 0 {
from, err := R1C1ToA1(refs[0])
if err != nil {
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
}
fromRef = from
if len(refs) == 2 {
to, err := R1C1ToA1(refs[1])
if err != nil {
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
}
toRef = to
}
}
if len(refs) == 1 {
value, err := fn.f.GetCellValue(fn.sheet, fromRef)
if err != nil {
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
}
return newStringFormulaArg(value)
}
arg, _ := fn.f.parseReference(fn.ctx, fn.sheet, fromRef+":"+toRef)
return arg
}
// LOOKUP function performs an approximate match lookup in a one-column or
// one-row range, and returns the corresponding value from another one-column
// or one-row range. The syntax of the function is:
//
// LOOKUP(lookup_value,lookup_vector,[result_vector])
func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg {
arrayForm, lookupValue, lookupVector, errArg := checkLookupArgs(argsList)
if errArg.Type == ArgError {
return errArg
}
cols, matchIdx, ok := iterateLookupArgs(lookupValue, lookupVector)
if ok && matchIdx == -1 {
matchIdx = len(cols) - 1
}
var column []formulaArg
if argsList.Len() == 3 {
column = lookupCol(argsList.Back().Value.(formulaArg), 0)
} else if arrayForm && len(lookupVector.Matrix[0]) > 1 {
column = lookupCol(lookupVector, 1)
} else {
column = cols
}
if matchIdx < 0 || matchIdx >= len(column) {
return newErrorFormulaArg(formulaErrorNA, "LOOKUP no result found")
}
return column[matchIdx]
}
// lookupCol extract columns for LOOKUP.
func lookupCol(arr formulaArg, idx int) []formulaArg {
col := arr.List
if arr.Type == ArgMatrix {
col = nil
for _, r := range arr.Matrix {
if len(r) > 0 {
col = append(col, r[idx])
continue
}
col = append(col, newEmptyFormulaArg())
}
}
return col
}
// ROW function returns the first row number within a supplied reference or
// the number of the current row. The syntax of the function is:
//
// ROW([reference])
func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg {
if argsList.Len() > 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ROW requires at most 1 argument")
}
if argsList.Len() == 1 {
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Row))
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Row))
}
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
}
_, row, _ := CellNameToCoordinates(fn.cell)
return newNumberFormulaArg(float64(row))
}
// ROWS function takes an Excel range and returns the number of rows that are
// contained within the range. The syntax of the function is:
//
// ROWS(array)
func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
}
minVal, maxVal := calcColsRowsMinMax(false, argsList)
if maxVal == TotalRows {
return newNumberFormulaArg(TotalRows)
}
result := maxVal - minVal + 1
if maxVal == minVal {
if minVal == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
}
return newNumberFormulaArg(float64(1))
}
return newNumberFormulaArg(float64(result))
}
// Web Functions
// ENCODEURL function returns a URL-encoded string, replacing certain
// non-alphanumeric characters with the percentage symbol (%) and a
// hexadecimal number. The syntax of the function is:
//
// ENCODEURL(url)
func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument")
}
token := argsList.Front().Value.(formulaArg).Value()
return newStringFormulaArg(strings.ReplaceAll(url.QueryEscape(token), "+", "%20"))
}
// Financial Functions
// validateFrequency check the number of coupon payments per year if be equal to 1, 2 or 4.
func validateFrequency(freq float64) bool {
return freq == 1 || freq == 2 || freq == 4
}
// ACCRINT function returns the accrued interest in a security that pays
// periodic interest. The syntax of the function is:
//
// ACCRINT(issue,first_interest,settlement,rate,par,frequency,[basis],[calc_method])
func (fn *formulaFuncs) ACCRINT(argsList *list.List) formulaArg {
if argsList.Len() < 6 {
return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT requires at least 6 arguments")
}
if argsList.Len() > 8 {
return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT allows at most 8 arguments")
}
args := fn.prepareDataValueArgs(3, argsList)
if args.Type != ArgList {
return args
}
issue, settlement := args.List[0], args.List[2]
rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
par := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber || par.Type != ArgNumber || frequency.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() >= 7 {
if basis = argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
if argsList.Len() == 8 {
if cm := argsList.Back().Value.(formulaArg).ToBool(); cm.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
frac1 := yearFrac(issue.Number, settlement.Number, int(basis.Number))
if frac1.Type != ArgNumber {
return frac1
}
return newNumberFormulaArg(par.Number * rate.Number * frac1.Number)
}
// ACCRINTM function returns the accrued interest in a security that pays
// interest at maturity. The syntax of the function is:
//
// ACCRINTM(issue,settlement,rate,[par],[basis])
func (fn *formulaFuncs) ACCRINTM(argsList *list.List) formulaArg {
if argsList.Len() != 4 && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, "ACCRINTM requires 4 or 5 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
issue, settlement := args.List[0], args.List[1]
if settlement.Number < issue.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
par := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber || par.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if par.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(issue.Number, settlement.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
return newNumberFormulaArg(frac.Number * rate.Number * par.Number)
}
// prepareAmorArgs checking and prepare arguments for the formula functions
// AMORDEGRC and AMORLINC.
func (fn *formulaFuncs) prepareAmorArgs(name string, argsList *list.List) formulaArg {
cost := argsList.Front().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost to be number argument", name))
}
if cost.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost >= 0", name))
}
args := list.New().Init()
args.PushBack(argsList.Front().Next().Value.(formulaArg))
datePurchased := fn.DATEVALUE(args)
if datePurchased.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args.Init()
args.PushBack(argsList.Front().Next().Next().Value.(formulaArg))
firstPeriod := fn.DATEVALUE(args)
if firstPeriod.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if firstPeriod.Number < datePurchased.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
salvage := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if salvage.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if salvage.Number < 0 || salvage.Number > cost.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
period := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if period.Type != ArgNumber || period.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
rate := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber || rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 7 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{cost, datePurchased, firstPeriod, salvage, period, rate, basis})
}
// AMORDEGRC function is provided for users of the French accounting system.
// The function calculates the prorated linear depreciation of an asset for a
// specified accounting period. The syntax of the function is:
//
// AMORDEGRC(cost,date_purchased,first_period,salvage,period,rate,[basis])
func (fn *formulaFuncs) AMORDEGRC(argsList *list.List) formulaArg {
if argsList.Len() != 6 && argsList.Len() != 7 {
return newErrorFormulaArg(formulaErrorVALUE, "AMORDEGRC requires 6 or 7 arguments")
}
args := fn.prepareAmorArgs("AMORDEGRC", argsList)
if args.Type != ArgList {
return args
}
cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6]
if rate.Number >= 0.5 {
return newErrorFormulaArg(formulaErrorNUM, "AMORDEGRC requires rate to be < 0.5")
}
assetsLife, amorCoeff := 1/rate.Number, 2.5
if assetsLife < 3 {
amorCoeff = 1
} else if assetsLife < 5 {
amorCoeff = 1.5
} else if assetsLife <= 6 {
amorCoeff = 2
}
rate.Number *= amorCoeff
frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
nRate := float64(int((frac.Number * cost.Number * rate.Number) + 0.5))
cost.Number -= nRate
rest := cost.Number - salvage.Number
for n := 0; n < int(period.Number); n++ {
nRate = float64(int((cost.Number * rate.Number) + 0.5))
rest -= nRate
if rest < 0 {
switch int(period.Number) - n {
case 0:
case 1:
return newNumberFormulaArg(float64(int((cost.Number * 0.5) + 0.5)))
default:
return newNumberFormulaArg(0)
}
}
cost.Number -= nRate
}
return newNumberFormulaArg(nRate)
}
// AMORLINC function is provided for users of the French accounting system.
// The function calculates the prorated linear depreciation of an asset for a
// specified accounting period. The syntax of the function is:
//
// AMORLINC(cost,date_purchased,first_period,salvage,period,rate,[basis])
func (fn *formulaFuncs) AMORLINC(argsList *list.List) formulaArg {
if argsList.Len() != 6 && argsList.Len() != 7 {
return newErrorFormulaArg(formulaErrorVALUE, "AMORLINC requires 6 or 7 arguments")
}
args := fn.prepareAmorArgs("AMORLINC", argsList)
if args.Type != ArgList {
return args
}
cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6]
frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
rate1 := frac.Number * cost.Number * rate.Number
if period.Number == 0 {
return newNumberFormulaArg(rate1)
}
rate2 := cost.Number * rate.Number
delta := cost.Number - salvage.Number
periods := int((delta - rate1) / rate2)
if int(period.Number) <= periods {
return newNumberFormulaArg(rate2)
} else if int(period.Number)-1 == periods {
return newNumberFormulaArg(delta - rate2*float64(periods) - math.Nextafter(rate1, rate1))
}
return newNumberFormulaArg(0)
}
// prepareCouponArgs checking and prepare arguments for the formula functions
// COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPPCD, COUPNUM and COUPNCD.
func (fn *formulaFuncs) prepareCouponArgs(name string, argsList *list.List) formulaArg {
if argsList.Len() != 3 && argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 or 4 arguments", name))
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
if settlement.Number >= maturity.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
}
frequency := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if frequency.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 4 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{settlement, maturity, frequency, basis})
}
// is30BasisMethod determine if the financial day count basis rules is 30/360
// methods.
func is30BasisMethod(basis int) bool {
return basis == 0 || basis == 4
}
// getDaysInMonthRange return the day by given year, month range and day count
// basis.
func getDaysInMonthRange(fromMonth, toMonth int) int {
if fromMonth > toMonth {
return 0
}
return (toMonth - fromMonth + 1) * 30
}
// getDayOnBasis returns the day by given date and day count basis.
func getDayOnBasis(y, m, d, basis int) int {
if !is30BasisMethod(basis) {
return d
}
day := d
dim := getDaysInMonth(y, m)
if day > 30 || d >= dim || day >= dim {
day = 30
}
return day
}
// coupdays returns the number of days that base on date range and the day
// count basis to be used.
func coupdays(from, to time.Time, basis int) float64 {
days := 0
fromY, fromM, fromD := from.Date()
toY, toM, toD := to.Date()
fromDay, toDay := getDayOnBasis(fromY, int(fromM), fromD, basis), getDayOnBasis(toY, int(toM), toD, basis)
if !is30BasisMethod(basis) {
return (daysBetween(excelMinTime1900.Unix(), makeDate(toY, toM, toDay)) + 1) - (daysBetween(excelMinTime1900.Unix(), makeDate(fromY, fromM, fromDay)) + 1)
}
if basis == 0 {
if (int(fromM) == 2 || fromDay < 30) && toD == 31 {
toDay = 31
}
} else {
if int(fromM) == 2 && fromDay == 30 {
fromDay = getDaysInMonth(fromY, 2)
}
if int(toM) == 2 && toDay == 30 {
toDay = getDaysInMonth(toY, 2)
}
}
if fromY < toY || (fromY == toY && int(fromM) < int(toM)) {
days = 30 - fromDay + 1
fromD = 1
fromDay = 1
date := time.Date(fromY, fromM, fromD, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)
if date.Year() < toY {
days += getDaysInMonthRange(int(date.Month()), 12)
date = date.AddDate(0, 13-int(date.Month()), 0)
}
days += getDaysInMonthRange(int(date.Month()), int(toM)-1)
}
if days += toDay - fromDay; days > 0 {
return float64(days)
}
return 0
}
// COUPDAYBS function calculates the number of days from the beginning of a
// coupon's period to the settlement date. The syntax of the function is:
//
// COUPDAYBS(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPDAYBS(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPDAYBS", argsList)
if args.Type != ArgList {
return args
}
settlement := timeFromExcelTime(args.List[0].Number, false)
pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false)
return newNumberFormulaArg(coupdays(pcd, settlement, int(args.List[3].Number)))
}
// COUPDAYS function calculates the number of days in a coupon period that
// contains the settlement date. The syntax of the function is:
//
// COUPDAYS(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPDAYS(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPDAYS", argsList)
if args.Type != ArgList {
return args
}
freq := args.List[2].Number
basis := int(args.List[3].Number)
if basis == 1 {
pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false)
next := pcd.AddDate(0, 12/int(freq), 0)
return newNumberFormulaArg(coupdays(pcd, next, basis))
}
return newNumberFormulaArg(float64(getYearDays(0, basis)) / freq)
}
// COUPDAYSNC function calculates the number of days from the settlement date
// to the next coupon date. The syntax of the function is:
//
// COUPDAYSNC(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPDAYSNC", argsList)
if args.Type != ArgList {
return args
}
settlement := timeFromExcelTime(args.List[0].Number, false)
basis := int(args.List[3].Number)
ncd := timeFromExcelTime(fn.COUPNCD(argsList).Number, false)
return newNumberFormulaArg(coupdays(settlement, ncd, basis))
}
// coupons is an implementation of the formula functions COUPNCD and COUPPCD.
func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg {
settlement := timeFromExcelTime(arg.List[0].Number, false)
maturity := timeFromExcelTime(arg.List[1].Number, false)
maturityDays := (maturity.Year()-settlement.Year())*12 + (int(maturity.Month()) - int(settlement.Month()))
coupon := 12 / int(arg.List[2].Number)
mod := maturityDays % coupon
year := settlement.Year()
month := int(settlement.Month())
if mod == 0 && settlement.Day() >= maturity.Day() {
month += coupon
} else {
month += mod
}
if name != "COUPNCD" {
month -= coupon
}
if month > 11 {
year++
month -= 12
} else if month < 0 {
year--
month += 12
}
day, lastDay := maturity.Day(), time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
days := getDaysInMonth(lastDay.Year(), int(lastDay.Month()))
if getDaysInMonth(maturity.Year(), int(maturity.Month())) == maturity.Day() {
day = days
} else if day > 27 && day > days {
day = days
}
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(year, time.Month(month), day)) + 1)
}
// COUPNCD function calculates the number of coupons payable, between a
// security's settlement date and maturity date, rounded up to the nearest
// whole coupon. The syntax of the function is:
//
// COUPNCD(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPNCD(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPNCD", argsList)
if args.Type != ArgList {
return args
}
return fn.coupons("COUPNCD", args)
}
// COUPNUM function calculates the number of coupons payable, between a
// security's settlement date and maturity date, rounded up to the nearest
// whole coupon. The syntax of the function is:
//
// COUPNUM(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPNUM(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPNUM", argsList)
if args.Type != ArgList {
return args
}
frac := yearFrac(args.List[0].Number, args.List[1].Number, 0)
return newNumberFormulaArg(math.Ceil(frac.Number * args.List[2].Number))
}
// COUPPCD function returns the previous coupon date, before the settlement
// date for a security. The syntax of the function is:
//
// COUPPCD(settlement,maturity,frequency,[basis])
func (fn *formulaFuncs) COUPPCD(argsList *list.List) formulaArg {
args := fn.prepareCouponArgs("COUPPCD", argsList)
if args.Type != ArgList {
return args
}
return fn.coupons("COUPPCD", args)
}
// CUMIPMT function calculates the cumulative interest paid on a loan or
// investment, between two specified periods. The syntax of the function is:
//
// CUMIPMT(rate,nper,pv,start_period,end_period,type)
func (fn *formulaFuncs) CUMIPMT(argsList *list.List) formulaArg {
return fn.cumip("CUMIPMT", argsList)
}
// CUMPRINC function calculates the cumulative payment on the principal of a
// loan or investment, between two specified periods. The syntax of the
// function is:
//
// CUMPRINC(rate,nper,pv,start_period,end_period,type)
func (fn *formulaFuncs) CUMPRINC(argsList *list.List) formulaArg {
return fn.cumip("CUMPRINC", argsList)
}
// cumip is an implementation of the formula functions CUMIPMT and CUMPRINC.
func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg {
if argsList.Len() != 6 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 arguments", name))
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
start := argsList.Back().Prev().Prev().Value.(formulaArg).ToNumber()
if start.Type != ArgNumber {
return start
}
end := argsList.Back().Prev().Value.(formulaArg).ToNumber()
if end.Type != ArgNumber {
return end
}
typ := argsList.Back().Value.(formulaArg).ToNumber()
if typ.Type != ArgNumber {
return typ
}
if typ.Number != 0 && typ.Number != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if start.Number < 1 || start.Number > end.Number {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
num := 0.0
for per := start.Number; per <= end.Number; per++ {
args := list.New().Init()
args.PushBack(rate)
args.PushBack(newNumberFormulaArg(per))
args.PushBack(nper)
args.PushBack(pv)
args.PushBack(newNumberFormulaArg(0))
args.PushBack(typ)
if name == "CUMIPMT" {
num += fn.IPMT(args).Number
continue
}
num += fn.PPMT(args).Number
}
return newNumberFormulaArg(num)
}
// calcDbArgsCompare implements common arguments' comparison for DB and DDB.
func calcDbArgsCompare(cost, salvage, life, period formulaArg) bool {
return (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1)
}
// DB function calculates the depreciation of an asset, using the Fixed
// Declining Balance Method, for each period of the asset's lifetime. The
// syntax of the function is:
//
// DB(cost,salvage,life,period,[month])
func (fn *formulaFuncs) DB(argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, "DB requires at least 4 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "DB allows at most 5 arguments")
}
cost := argsList.Front().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber {
return cost
}
salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
if salvage.Type != ArgNumber {
return salvage
}
life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if life.Type != ArgNumber {
return life
}
period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if period.Type != ArgNumber {
return period
}
month := newNumberFormulaArg(12)
if argsList.Len() == 5 {
if month = argsList.Back().Value.(formulaArg).ToNumber(); month.Type != ArgNumber {
return month
}
}
if cost.Number == 0 {
return newNumberFormulaArg(0)
}
if calcDbArgsCompare(cost, salvage, life, period) || (month.Number < 1) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
dr := 1 - math.Pow(salvage.Number/cost.Number, 1/life.Number)
dr = math.Round(dr*1000) / 1000
pd, depreciation := 0.0, 0.0
for per := 1; per <= int(period.Number); per++ {
if per == 1 {
depreciation = cost.Number * dr * month.Number / 12
} else if per == int(life.Number+1) {
depreciation = (cost.Number - pd) * dr * (12 - month.Number) / 12
} else {
depreciation = (cost.Number - pd) * dr
}
pd += depreciation
}
return newNumberFormulaArg(depreciation)
}
// DDB function calculates the depreciation of an asset, using the Double
// Declining Balance Method, or another specified depreciation rate. The
// syntax of the function is:
//
// DDB(cost,salvage,life,period,[factor])
func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, "DDB requires at least 4 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "DDB allows at most 5 arguments")
}
cost := argsList.Front().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber {
return cost
}
salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
if salvage.Type != ArgNumber {
return salvage
}
life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if life.Type != ArgNumber {
return life
}
period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if period.Type != ArgNumber {
return period
}
factor := newNumberFormulaArg(2)
if argsList.Len() == 5 {
if factor = argsList.Back().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber {
return factor
}
}
if cost.Number == 0 {
return newNumberFormulaArg(0)
}
if calcDbArgsCompare(cost, salvage, life, period) || (factor.Number <= 0.0) || (period.Number > life.Number) {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
pd, depreciation := 0.0, 0.0
for per := 1; per <= int(period.Number); per++ {
depreciation = math.Min((cost.Number-pd)*(factor.Number/life.Number), cost.Number-salvage.Number-pd)
pd += depreciation
}
return newNumberFormulaArg(depreciation)
}
// prepareDataValueArgs convert first N arguments to data value for the
// formula functions.
func (fn *formulaFuncs) prepareDataValueArgs(n int, argsList *list.List) formulaArg {
l := list.New()
var dataValues []formulaArg
getDateValue := func(arg formulaArg, l *list.List) formulaArg {
switch arg.Type {
case ArgNumber:
break
case ArgString:
num := arg.ToNumber()
if num.Type == ArgNumber {
arg = num
break
}
l.Init()
l.PushBack(arg)
arg = fn.DATEVALUE(l)
if arg.Type == ArgError {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
default:
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return arg
}
for i, arg := 0, argsList.Front(); i < n; arg = arg.Next() {
dataValue := getDateValue(arg.Value.(formulaArg), l)
if dataValue.Type != ArgNumber {
return dataValue
}
dataValues = append(dataValues, dataValue)
i++
}
return newListFormulaArg(dataValues)
}
// discIntrate is an implementation of the formula functions DISC and INTRATE.
func (fn *formulaFuncs) discIntrate(name string, argsList *list.List) formulaArg {
if argsList.Len() != 4 && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 or 5 arguments", name))
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity, argName := args.List[0], args.List[1], "pr"
if maturity.Number <= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
}
prInvestment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if prInvestment.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if prInvestment.Number <= 0 {
if name == "INTRATE" {
argName = "investment"
}
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires %s > 0", name, argName))
}
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if redemption.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
if name == "INTRATE" {
return newNumberFormulaArg((redemption.Number - prInvestment.Number) / prInvestment.Number / frac.Number)
}
return newNumberFormulaArg((redemption.Number - prInvestment.Number) / redemption.Number / frac.Number)
}
// DISC function calculates the Discount Rate for a security. The syntax of
// the function is:
//
// DISC(settlement,maturity,pr,redemption,[basis])
func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg {
return fn.discIntrate("DISC", argsList)
}
// DOLLAR function rounds a supplied number to a specified number of decimal
// places and then converts this into a text string with a currency format. The
// syntax of the function is:
//
// DOLLAR(number,[decimals])
func (fn *formulaFuncs) DOLLAR(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires 1 or 2 arguments")
}
numArg := argsList.Front().Value.(formulaArg)
n := numArg.ToNumber()
if n.Type != ArgNumber {
return n
}
decimals, dot, value := 2, ".", numArg.Value()
if argsList.Len() == 2 {
d := argsList.Back().Value.(formulaArg).ToNumber()
if d.Type != ArgNumber {
return d
}
if d.Number < 0 {
value = strconv.FormatFloat(fn.round(n.Number, d.Number, down), 'f', -1, 64)
}
if d.Number >= 128 {
return newErrorFormulaArg(formulaErrorVALUE, "decimal value should be less than 128")
}
if decimals = int(d.Number); decimals < 0 {
decimals, dot = 0, ""
}
}
symbol := map[CultureName]string{
CultureNameUnknown: "$",
CultureNameEnUS: "$",
CultureNameJaJP: "¥",
CultureNameKoKR: "\u20a9",
CultureNameZhCN: "¥",
CultureNameZhTW: "NT$",
}[fn.f.options.CultureInfo]
numFmtCode := fmt.Sprintf("%s#,##0%s%s;(%s#,##0%s%s)",
symbol, dot, strings.Repeat("0", decimals), symbol, dot, strings.Repeat("0", decimals))
return newStringFormulaArg(format(value, numFmtCode, false, CellTypeNumber, nil))
}
// DOLLARDE function converts a dollar value in fractional notation, into a
// dollar value expressed as a decimal. The syntax of the function is:
//
// DOLLARDE(fractional_dollar,fraction)
func (fn *formulaFuncs) DOLLARDE(argsList *list.List) formulaArg {
return fn.dollar("DOLLARDE", argsList)
}
// DOLLARFR function converts a dollar value in decimal notation, into a
// dollar value that is expressed in fractional notation. The syntax of the
// function is:
//
// DOLLARFR(decimal_dollar,fraction)
func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg {
return fn.dollar("DOLLARFR", argsList)
}
// dollar is an implementation of the formula functions DOLLARDE and DOLLARFR.
func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
}
dollar := argsList.Front().Value.(formulaArg).ToNumber()
if dollar.Type != ArgNumber {
return dollar
}
frac := argsList.Back().Value.(formulaArg).ToNumber()
if frac.Type != ArgNumber {
return frac
}
if frac.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if frac.Number == 0 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
cents := math.Mod(dollar.Number, 1)
if name == "DOLLARDE" {
cents /= frac.Number
cents *= math.Pow(10, math.Ceil(math.Log10(frac.Number)))
} else {
cents *= frac.Number
cents *= math.Pow(10, -math.Ceil(math.Log10(frac.Number)))
}
return newNumberFormulaArg(math.Floor(dollar.Number) + cents)
}
// prepareDurationArgs checking and prepare arguments for the formula
// functions DURATION and MDURATION.
func (fn *formulaFuncs) prepareDurationArgs(name string, argsList *list.List) formulaArg {
if argsList.Len() != 5 && argsList.Len() != 6 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 5 or 6 arguments", name))
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
if settlement.Number >= maturity.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
}
coupon := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if coupon.Type != ArgNumber {
return coupon
}
if coupon.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires coupon >= 0", name))
}
yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if yld.Type != ArgNumber {
return yld
}
if yld.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name))
}
frequency := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if frequency.Type != ArgNumber {
return frequency
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 6 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{settlement, maturity, coupon, yld, frequency, basis})
}
// duration is an implementation of the formula function DURATION.
func (fn *formulaFuncs) duration(settlement, maturity, coupon, yld, frequency, basis formulaArg) formulaArg {
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
argumments := list.New().Init()
argumments.PushBack(settlement)
argumments.PushBack(maturity)
argumments.PushBack(frequency)
argumments.PushBack(basis)
coups := fn.COUPNUM(argumments)
duration := 0.0
p := 0.0
coupon.Number *= 100 / frequency.Number
yld.Number /= frequency.Number
yld.Number++
diff := frac.Number*frequency.Number - coups.Number
for t := 1.0; t < coups.Number; t++ {
tDiff := t + diff
add := coupon.Number / math.Pow(yld.Number, tDiff)
p += add
duration += tDiff * add
}
add := (coupon.Number + 100) / math.Pow(yld.Number, coups.Number+diff)
p += add
duration += (coups.Number + diff) * add
duration /= p
duration /= frequency.Number
return newNumberFormulaArg(duration)
}
// DURATION function calculates the Duration (specifically, the Macaulay
// Duration) of a security that pays periodic interest, assuming a par value
// of $100. The syntax of the function is:
//
// DURATION(settlement,maturity,coupon,yld,frequency,[basis])
func (fn *formulaFuncs) DURATION(argsList *list.List) formulaArg {
args := fn.prepareDurationArgs("DURATION", argsList)
if args.Type != ArgList {
return args
}
return fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5])
}
// EFFECT function returns the effective annual interest rate for a given
// nominal interest rate and number of compounding periods per year. The
// syntax of the function is:
//
// EFFECT(nominal_rate,npery)
func (fn *formulaFuncs) EFFECT(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "EFFECT requires 2 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
npery := argsList.Back().Value.(formulaArg).ToNumber()
if npery.Type != ArgNumber {
return npery
}
if rate.Number <= 0 || npery.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(math.Pow(1+rate.Number/npery.Number, npery.Number) - 1)
}
// EUROCONVERT function convert a number to euro or from euro to a
// participating currency. You can also use it to convert a number from one
// participating currency to another by using the euro as an intermediary
// (triangulation). The syntax of the function is:
//
// EUROCONVERT(number,sourcecurrency,targetcurrency[,fullprecision,triangulationprecision])
func (fn *formulaFuncs) EUROCONVERT(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "EUROCONVERT requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "EUROCONVERT allows at most 5 arguments")
}
number := argsList.Front().Value.(formulaArg).ToNumber()
if number.Type != ArgNumber {
return number
}
sourceCurrency := argsList.Front().Next().Value.(formulaArg).Value()
targetCurrency := argsList.Front().Next().Next().Value.(formulaArg).Value()
fullPrec, triangulationPrec := newBoolFormulaArg(false), newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if fullPrec = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool(); fullPrec.Type != ArgNumber {
return fullPrec
}
}
if argsList.Len() == 5 {
if triangulationPrec = argsList.Back().Value.(formulaArg).ToNumber(); triangulationPrec.Type != ArgNumber {
return triangulationPrec
}
}
convertTable := map[string][]float64{
"EUR": {1.0, 2},
"ATS": {13.7603, 2},
"BEF": {40.3399, 0},
"DEM": {1.95583, 2},
"ESP": {166.386, 0},
"FIM": {5.94573, 2},
"FRF": {6.55957, 2},
"IEP": {0.787564, 2},
"ITL": {1936.27, 0},
"LUF": {40.3399, 0},
"NLG": {2.20371, 2},
"PTE": {200.482, 2},
"GRD": {340.750, 2},
"SIT": {239.640, 2},
"MTL": {0.429300, 2},
"CYP": {0.585274, 2},
"SKK": {30.1260, 2},
"EEK": {15.6466, 2},
"LVL": {0.702804, 2},
"LTL": {3.45280, 2},
}
source, ok := convertTable[sourceCurrency]
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
target, ok := convertTable[targetCurrency]
if !ok {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if sourceCurrency == targetCurrency {
return number
}
var res float64
if sourceCurrency == "EUR" {
res = number.Number * target[0]
} else {
intermediate := number.Number / source[0]
if triangulationPrec.Number != 0 {
ratio := math.Pow(10, triangulationPrec.Number)
intermediate = math.Round(intermediate*ratio) / ratio
}
res = intermediate * target[0]
}
if fullPrec.Number != 1 {
ratio := math.Pow(10, target[1])
res = math.Round(res*ratio) / ratio
}
return newNumberFormulaArg(res)
}
// FV function calculates the Future Value of an investment with periodic
// constant payments and a constant interest rate. The syntax of the function
// is:
//
// FV(rate,nper,[pmt],[pv],[type])
func (fn *formulaFuncs) FV(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "FV requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "FV allows at most 5 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pmt.Type != ArgNumber {
return pmt
}
pv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if pv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); pv.Type != ArgNumber {
return pv
}
}
if argsList.Len() == 5 {
if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
return typ
}
}
if typ.Number != 0 && typ.Number != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if rate.Number != 0 {
return newNumberFormulaArg(-pv.Number*math.Pow(1+rate.Number, nper.Number) - pmt.Number*(1+rate.Number*typ.Number)*(math.Pow(1+rate.Number, nper.Number)-1)/rate.Number)
}
return newNumberFormulaArg(-pv.Number - pmt.Number*nper.Number)
}
// FVSCHEDULE function calculates the Future Value of an investment with a
// variable interest rate. The syntax of the function is:
//
// FVSCHEDULE(principal,schedule)
func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "FVSCHEDULE requires 2 arguments")
}
pri := argsList.Front().Value.(formulaArg).ToNumber()
if pri.Type != ArgNumber {
return pri
}
principal := pri.Number
for _, arg := range argsList.Back().Value.(formulaArg).ToList() {
if arg.Value() == "" {
continue
}
rate := arg.ToNumber()
if rate.Type != ArgNumber {
return rate
}
principal *= 1 + rate.Number
}
return newNumberFormulaArg(principal)
}
// INTRATE function calculates the interest rate for a fully invested
// security. The syntax of the function is:
//
// INTRATE(settlement,maturity,investment,redemption,[basis])
func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg {
return fn.discIntrate("INTRATE", argsList)
}
// IPMT function calculates the interest payment, during a specific period of a
// loan or investment that is paid in constant periodic payments, with a
// constant interest rate. The syntax of the function is:
//
// IPMT(rate,per,nper,pv,[fv],[type])
func (fn *formulaFuncs) IPMT(argsList *list.List) formulaArg {
return fn.ipmt("IPMT", argsList)
}
// calcIpmt is part of the implementation ipmt.
func calcIpmt(name string, typ, per, pmt, pv, rate formulaArg) formulaArg {
capital, interest, principal := pv.Number, 0.0, 0.0
for i := 1; i <= int(per.Number); i++ {
if typ.Number != 0 && i == 1 {
interest = 0
} else {
interest = -capital * rate.Number
}
principal = pmt.Number - interest
capital += principal
}
if name == "IPMT" {
return newNumberFormulaArg(interest)
}
return newNumberFormulaArg(principal)
}
// ipmt is an implementation of the formula functions IPMT and PPMT.
func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 4 arguments", name))
}
if argsList.Len() > 6 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 6 arguments", name))
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
per := argsList.Front().Next().Value.(formulaArg).ToNumber()
if per.Type != ArgNumber {
return per
}
nper := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pv := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
if argsList.Len() >= 5 {
if fv = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
return fv
}
}
if argsList.Len() == 6 {
if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
return typ
}
}
if typ.Number != 0 && typ.Number != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if per.Number <= 0 || per.Number > nper.Number {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
args := list.New().Init()
args.PushBack(rate)
args.PushBack(nper)
args.PushBack(pv)
args.PushBack(fv)
args.PushBack(typ)
pmt := fn.PMT(args)
return calcIpmt(name, typ, per, pmt, pv, rate)
}
// IRR function returns the Internal Rate of Return for a supplied series of
// periodic cash flows (i.e. an initial investment value and a series of net
// income values). The syntax of the function is:
//
// IRR(values,[guess])
func (fn *formulaFuncs) IRR(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "IRR requires at least 1 argument")
}
if argsList.Len() > 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IRR allows at most 2 arguments")
}
values, guess := argsList.Front().Value.(formulaArg).ToList(), newNumberFormulaArg(0.1)
if argsList.Len() > 1 {
if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber {
return guess
}
}
x1, x2 := newNumberFormulaArg(0), guess
args := list.New().Init()
args.PushBack(x1)
for _, v := range values {
args.PushBack(v)
}
f1 := fn.NPV(args)
args.Front().Value = x2
f2 := fn.NPV(args)
for i := 0; i < maxFinancialIterations; i++ {
if f1.Number*f2.Number < 0 {
break
}
if math.Abs(f1.Number) < math.Abs(f2.Number) {
x1.Number += 1.6 * (x1.Number - x2.Number)
args.Front().Value = x1
f1 = fn.NPV(args)
continue
}
x2.Number += 1.6 * (x2.Number - x1.Number)
args.Front().Value = x2
f2 = fn.NPV(args)
}
if f1.Number*f2.Number > 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
args.Front().Value = x1
f := fn.NPV(args)
var rtb, dx, xMid, fMid float64
if f.Number < 0 {
rtb = x1.Number
dx = x2.Number - x1.Number
} else {
rtb = x2.Number
dx = x1.Number - x2.Number
}
for i := 0; i < maxFinancialIterations; i++ {
dx *= 0.5
xMid = rtb + dx
args.Front().Value = newNumberFormulaArg(xMid)
fMid = fn.NPV(args).Number
if fMid <= 0 {
rtb = xMid
}
if math.Abs(fMid) < financialPrecision || math.Abs(dx) < financialPrecision {
break
}
}
return newNumberFormulaArg(xMid)
}
// ISPMT function calculates the interest paid during a specific period of a
// loan or investment. The syntax of the function is:
//
// ISPMT(rate,per,nper,pv)
func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "ISPMT requires 4 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
per := argsList.Front().Next().Value.(formulaArg).ToNumber()
if per.Type != ArgNumber {
return per
}
nper := argsList.Back().Prev().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pv := argsList.Back().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
pr, payment, num := pv.Number, pv.Number/nper.Number, 0.0
for i := 0; i <= int(per.Number); i++ {
num = rate.Number * pr * -1
pr -= payment
if i == int(nper.Number) {
num = 0
}
}
return newNumberFormulaArg(num)
}
// MDURATION function calculates the Modified Macaulay Duration of a security
// that pays periodic interest, assuming a par value of $100. The syntax of
// the function is:
//
// MDURATION(settlement,maturity,coupon,yld,frequency,[basis])
func (fn *formulaFuncs) MDURATION(argsList *list.List) formulaArg {
args := fn.prepareDurationArgs("MDURATION", argsList)
if args.Type != ArgList {
return args
}
duration := fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5])
if duration.Type != ArgNumber {
return duration
}
return newNumberFormulaArg(duration.Number / (1 + args.List[3].Number/args.List[4].Number))
}
// MIRR function returns the Modified Internal Rate of Return for a supplied
// series of periodic cash flows (i.e. a set of values, which includes an
// initial investment value and a series of net income values). The syntax of
// the function is:
//
// MIRR(values,finance_rate,reinvest_rate)
func (fn *formulaFuncs) MIRR(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "MIRR requires 3 arguments")
}
values := argsList.Front().Value.(formulaArg).ToList()
financeRate := argsList.Front().Next().Value.(formulaArg).ToNumber()
if financeRate.Type != ArgNumber {
return financeRate
}
reinvestRate := argsList.Back().Value.(formulaArg).ToNumber()
if reinvestRate.Type != ArgNumber {
return reinvestRate
}
n, fr, rr, npvPos, npvNeg := len(values), 1+financeRate.Number, 1+reinvestRate.Number, 0.0, 0.0
for i, v := range values {
val := v.ToNumber()
if val.Number >= 0 {
npvPos += val.Number / math.Pow(rr, float64(i))
continue
}
npvNeg += val.Number / math.Pow(fr, float64(i))
}
if npvNeg == 0 || npvPos == 0 || reinvestRate.Number <= -1 {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
return newNumberFormulaArg(math.Pow(-npvPos*math.Pow(rr, float64(n))/(npvNeg*rr), 1/(float64(n)-1)) - 1)
}
// NOMINAL function returns the nominal interest rate for a given effective
// interest rate and number of compounding periods per year. The syntax of
// the function is:
//
// NOMINAL(effect_rate,npery)
func (fn *formulaFuncs) NOMINAL(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NOMINAL requires 2 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
npery := argsList.Back().Value.(formulaArg).ToNumber()
if npery.Type != ArgNumber {
return npery
}
if rate.Number <= 0 || npery.Number < 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(npery.Number * (math.Pow(rate.Number+1, 1/npery.Number) - 1))
}
// NPER function calculates the number of periods required to pay off a loan,
// for a constant periodic payment and a constant interest rate. The syntax
// of the function is:
//
// NPER(rate,pmt,pv,[fv],[type])
func (fn *formulaFuncs) NPER(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NPER requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "NPER allows at most 5 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
pmt := argsList.Front().Next().Value.(formulaArg).ToNumber()
if pmt.Type != ArgNumber {
return pmt
}
pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
return fv
}
}
if argsList.Len() == 5 {
if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
return typ
}
}
if typ.Number != 0 && typ.Number != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if pmt.Number == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if rate.Number != 0 {
p := math.Log((pmt.Number*(1+rate.Number*typ.Number)/rate.Number-fv.Number)/(pv.Number+pmt.Number*(1+rate.Number*typ.Number)/rate.Number)) / math.Log(1+rate.Number)
return newNumberFormulaArg(p)
}
return newNumberFormulaArg((-pv.Number - fv.Number) / pmt.Number)
}
// NPV function calculates the Net Present Value of an investment, based on a
// supplied discount rate, and a series of future payments and income. The
// syntax of the function is:
//
// NPV(rate,value1,[value2],[value3],...)
func (fn *formulaFuncs) NPV(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NPV requires at least 2 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
val, i := 0.0, 1
for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
num := arg.Value.(formulaArg).ToNumber()
if num.Type != ArgNumber {
continue
}
val += num.Number / math.Pow(1+rate.Number, float64(i))
i++
}
return newNumberFormulaArg(val)
}
// aggrBetween is a part of implementation of the formula function ODDFPRICE.
func aggrBetween(startPeriod, endPeriod float64, initialValue []float64, f func(acc []float64, index float64) []float64) []float64 {
var s []float64
if startPeriod <= endPeriod {
for i := startPeriod; i <= endPeriod; i++ {
s = append(s, i)
}
} else {
for i := startPeriod; i >= endPeriod; i-- {
s = append(s, i)
}
}
return fold(f, initialValue, s)
}
// fold is a part of implementation of the formula function ODDFPRICE.
func fold(f func(acc []float64, index float64) []float64, state []float64, source []float64) []float64 {
length, value := len(source), state
for index := 0; length > index; index++ {
value = f(value, source[index])
}
return value
}
// changeMonth is a part of implementation of the formula function ODDFPRICE.
func changeMonth(date time.Time, numMonths float64, returnLastMonth bool) time.Time {
offsetDay := 0
if returnLastMonth && date.Day() == getDaysInMonth(date.Year(), int(date.Month())) {
offsetDay--
}
newDate := date.AddDate(0, int(numMonths), offsetDay)
if returnLastMonth {
lastDay := getDaysInMonth(newDate.Year(), int(newDate.Month()))
return timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), makeDate(newDate.Year(), newDate.Month(), lastDay))+1, false)
}
return newDate
}
// datesAggregate is a part of implementation of the formula function
// ODDFPRICE.
func datesAggregate(startDate, endDate time.Time, numMonths float64, f func(pcd, ncd time.Time) float64, acc float64, returnLastMonth bool) (time.Time, time.Time, float64) {
frontDate, trailingDate := startDate, endDate
s1 := frontDate.After(endDate) || frontDate.Equal(endDate)
s2 := endDate.After(frontDate) || endDate.Equal(frontDate)
stop := s2
if numMonths > 0 {
stop = s1
}
for !stop {
trailingDate = frontDate
frontDate = changeMonth(frontDate, numMonths, returnLastMonth)
fn := f(frontDate, trailingDate)
acc += fn
s1 = frontDate.After(endDate) || frontDate.Equal(endDate)
s2 = endDate.After(frontDate) || endDate.Equal(frontDate)
stop = s2
if numMonths > 0 {
stop = s1
}
}
return frontDate, trailingDate, acc
}
// coupNumber is a part of implementation of the formula function ODDFPRICE.
func coupNumber(maturity, settlement, numMonths float64) float64 {
maturityTime, settlementTime := timeFromExcelTime(maturity, false), timeFromExcelTime(settlement, false)
my, mm, md := maturityTime.Year(), maturityTime.Month(), maturityTime.Day()
sy, sm, sd := settlementTime.Year(), settlementTime.Month(), settlementTime.Day()
couponsTemp, endOfMonthTemp := 0.0, getDaysInMonth(my, int(mm)) == md
endOfMonth := endOfMonthTemp
if !endOfMonthTemp && mm != 2 && md > 28 && md < getDaysInMonth(my, int(mm)) {
endOfMonth = getDaysInMonth(sy, int(sm)) == sd
}
startDate := changeMonth(settlementTime, 0, endOfMonth)
coupons := couponsTemp
if startDate.After(settlementTime) {
coupons++
}
date := changeMonth(startDate, numMonths, endOfMonth)
f := func(pcd, ncd time.Time) float64 {
return 1
}
_, _, result := datesAggregate(date, maturityTime, numMonths, f, coupons, endOfMonth)
return result
}
// prepareOddYldOrPrArg checking and prepare yield or price arguments for the
// formula functions ODDFPRICE, ODDFYIELD, ODDLPRICE and ODDLYIELD.
func prepareOddYldOrPrArg(name string, arg formulaArg) formulaArg {
yldOrPr := arg.ToNumber()
if yldOrPr.Type != ArgNumber {
return yldOrPr
}
if (name == "ODDFPRICE" || name == "ODDLPRICE") && yldOrPr.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name))
}
if (name == "ODDFYIELD" || name == "ODDLYIELD") && yldOrPr.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires pr > 0", name))
}
return yldOrPr
}
// prepareOddfArgs checking and prepare arguments for the formula
// functions ODDFPRICE and ODDFYIELD.
func (fn *formulaFuncs) prepareOddfArgs(name string, argsList *list.List) formulaArg {
dateValues := fn.prepareDataValueArgs(4, argsList)
if dateValues.Type != ArgList {
return dateValues
}
settlement, maturity, issue, firstCoupon := dateValues.List[0], dateValues.List[1], dateValues.List[2], dateValues.List[3]
if issue.Number >= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > issue", name))
}
if settlement.Number >= firstCoupon.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires first_coupon > settlement", name))
}
if firstCoupon.Number >= maturity.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > first_coupon", name))
}
rate := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
}
yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg))
if yldOrPr.Type != ArgNumber {
return yldOrPr
}
redemption := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if redemption.Type != ArgNumber {
return redemption
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
}
frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if frequency.Type != ArgNumber {
return frequency
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 9 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{settlement, maturity, issue, firstCoupon, rate, yldOrPr, redemption, frequency, basis})
}
// ODDFPRICE function calculates the price per $100 face value of a security
// with an odd (short or long) first period. The syntax of the function is:
//
// ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,[basis])
func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg {
if argsList.Len() != 8 && argsList.Len() != 9 {
return newErrorFormulaArg(formulaErrorVALUE, "ODDFPRICE requires 8 or 9 arguments")
}
args := fn.prepareOddfArgs("ODDFPRICE", argsList)
if args.Type != ArgList {
return args
}
settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8]
if basisArg.Number < 0 || basisArg.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
issueTime := timeFromExcelTime(issue.Number, false)
settlementTime := timeFromExcelTime(settlement.Number, false)
maturityTime := timeFromExcelTime(maturity.Number, false)
firstCouponTime := timeFromExcelTime(firstCoupon.Number, false)
basis := int(basisArg.Number)
monthDays := getDaysInMonth(maturityTime.Year(), int(maturityTime.Month()))
returnLastMonth := monthDays == maturityTime.Day()
numMonths := 12 / frequency.Number
numMonthsNeg := -numMonths
mat := changeMonth(maturityTime, numMonthsNeg, returnLastMonth)
pcd, _, _ := datesAggregate(mat, firstCouponTime, numMonthsNeg, func(d1, d2 time.Time) float64 {
return 0
}, 0, returnLastMonth)
if !pcd.Equal(firstCouponTime) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
fnArgs := list.New().Init()
fnArgs.PushBack(settlement)
fnArgs.PushBack(maturity)
fnArgs.PushBack(frequency)
fnArgs.PushBack(basisArg)
e := fn.COUPDAYS(fnArgs)
n := fn.COUPNUM(fnArgs)
m := frequency.Number
dfc := coupdays(issueTime, firstCouponTime, basis)
if dfc < e.Number {
dsc := coupdays(settlementTime, firstCouponTime, basis)
a := coupdays(issueTime, settlementTime, basis)
x := yld.Number/m + 1
y := dsc / e.Number
p1 := x
p3 := math.Pow(p1, n.Number-1+y)
term1 := redemption.Number / p3
term2 := 100 * rate.Number / m * dfc / e.Number / math.Pow(p1, y)
f := func(acc []float64, index float64) []float64 {
return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index-1+y)}
}
term3 := aggrBetween(2, math.Floor(n.Number), []float64{0}, f)
p2 := rate.Number / m
term4 := a / e.Number * p2 * 100
return newNumberFormulaArg(term1 + term2 + term3[0] - term4)
}
fnArgs.Init()
fnArgs.PushBack(issue)
fnArgs.PushBack(firstCoupon)
fnArgs.PushBack(frequency)
nc := fn.COUPNUM(fnArgs)
lastCoupon := firstCoupon.Number
aggrFunc := func(acc []float64, index float64) []float64 {
lastCouponTime := timeFromExcelTime(lastCoupon, false)
earlyCoupon := daysBetween(excelMinTime1900.Unix(), makeDate(lastCouponTime.Year(), time.Month(float64(lastCouponTime.Month())+numMonthsNeg), lastCouponTime.Day())) + 1
earlyCouponTime := timeFromExcelTime(earlyCoupon, false)
nl := e.Number
if basis == 1 {
nl = coupdays(earlyCouponTime, lastCouponTime, basis)
}
dci := coupdays(issueTime, lastCouponTime, basis)
if index > 1 {
dci = nl
}
startDate := earlyCoupon
if issue.Number > earlyCoupon {
startDate = issue.Number
}
endDate := lastCoupon
if settlement.Number < lastCoupon {
endDate = settlement.Number
}
startDateTime := timeFromExcelTime(startDate, false)
endDateTime := timeFromExcelTime(endDate, false)
a := coupdays(startDateTime, endDateTime, basis)
lastCoupon = earlyCoupon
dcnl := acc[0]
anl := acc[1]
return []float64{dcnl + dci/nl, anl + a/nl}
}
ag := aggrBetween(math.Floor(nc.Number), 1, []float64{0, 0}, aggrFunc)
dcnl, anl := ag[0], ag[1]
dsc := 0.0
fnArgs.Init()
fnArgs.PushBack(settlement)
fnArgs.PushBack(firstCoupon)
fnArgs.PushBack(frequency)
if basis == 2 || basis == 3 {
d := timeFromExcelTime(fn.COUPNCD(fnArgs).Number, false)
dsc = coupdays(settlementTime, d, basis)
} else {
d := timeFromExcelTime(fn.COUPPCD(fnArgs).Number, false)
a := coupdays(d, settlementTime, basis)
dsc = e.Number - a
}
nq := coupNumber(firstCoupon.Number, settlement.Number, numMonths)
fnArgs.Init()
fnArgs.PushBack(firstCoupon)
fnArgs.PushBack(maturity)
fnArgs.PushBack(frequency)
fnArgs.PushBack(basisArg)
n = fn.COUPNUM(fnArgs)
x := yld.Number/m + 1
y := dsc / e.Number
p1 := x
p3 := math.Pow(p1, y+nq+n.Number)
term1 := redemption.Number / p3
term2 := 100 * rate.Number / m * dcnl / math.Pow(p1, nq+y)
f := func(acc []float64, index float64) []float64 {
return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index+nq+y)}
}
term3 := aggrBetween(1, math.Floor(n.Number), []float64{0}, f)
term4 := 100 * rate.Number / m * anl
return newNumberFormulaArg(term1 + term2 + term3[0] - term4)
}
// getODDFPRICE is a part of implementation of the formula function ODDFPRICE.
func getODDFPRICE(f func(yld float64) float64, x, cnt, prec float64) float64 {
const maxCnt = 20.0
d := func(f func(yld float64) float64, x float64) float64 {
return (f(x+prec) - f(x-prec)) / (2 * prec)
}
fx, Fx := f(x), d(f, x)
newX := x - (fx / Fx)
if math.Abs(newX-x) < prec {
return newX
} else if cnt > maxCnt {
return newX
}
return getODDFPRICE(f, newX, cnt+1, prec)
}
// ODDFYIELD function calculates the yield of a security with an odd (short or
// long) first period. The syntax of the function is:
//
// ODDFYIELD(settlement,maturity,issue,first_coupon,rate,pr,redemption,frequency,[basis])
func (fn *formulaFuncs) ODDFYIELD(argsList *list.List) formulaArg {
if argsList.Len() != 8 && argsList.Len() != 9 {
return newErrorFormulaArg(formulaErrorVALUE, "ODDFYIELD requires 8 or 9 arguments")
}
args := fn.prepareOddfArgs("ODDFYIELD", argsList)
if args.Type != ArgList {
return args
}
settlement, maturity, issue, firstCoupon, rate, pr, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8]
if basisArg.Number < 0 || basisArg.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
settlementTime := timeFromExcelTime(settlement.Number, false)
maturityTime := timeFromExcelTime(maturity.Number, false)
years := coupdays(settlementTime, maturityTime, int(basisArg.Number))
px := pr.Number - 100
num := rate.Number*years*100 - px
denum := px/4 + years*px/2 + years*100
guess := num / denum
f := func(yld float64) float64 {
fnArgs := list.New().Init()
fnArgs.PushBack(settlement)
fnArgs.PushBack(maturity)
fnArgs.PushBack(issue)
fnArgs.PushBack(firstCoupon)
fnArgs.PushBack(rate)
fnArgs.PushBack(newNumberFormulaArg(yld))
fnArgs.PushBack(redemption)
fnArgs.PushBack(frequency)
fnArgs.PushBack(basisArg)
return pr.Number - fn.ODDFPRICE(fnArgs).Number
}
if result := getODDFPRICE(f, guess, 0, 1e-7); !math.IsInf(result, 0) {
return newNumberFormulaArg(result)
}
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
// prepareOddlArgs checking and prepare arguments for the formula
// functions ODDLPRICE and ODDLYIELD.
func (fn *formulaFuncs) prepareOddlArgs(name string, argsList *list.List) formulaArg {
dateValues := fn.prepareDataValueArgs(3, argsList)
if dateValues.Type != ArgList {
return dateValues
}
settlement, maturity, lastInterest := dateValues.List[0], dateValues.List[1], dateValues.List[2]
if lastInterest.Number >= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > last_interest", name))
}
if settlement.Number >= maturity.Number {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
}
rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
}
yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Value.(formulaArg))
if yldOrPr.Type != ArgNumber {
return yldOrPr
}
redemption := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if redemption.Type != ArgNumber {
return redemption
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
}
frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if frequency.Type != ArgNumber {
return frequency
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 8 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{settlement, maturity, lastInterest, rate, yldOrPr, redemption, frequency, basis})
}
// oddl is an implementation of the formula functions ODDLPRICE and ODDLYIELD.
func (fn *formulaFuncs) oddl(name string, argsList *list.List) formulaArg {
if argsList.Len() != 7 && argsList.Len() != 8 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 7 or 8 arguments", name))
}
args := fn.prepareOddlArgs(name, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity, lastInterest, rate, prOrYld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7]
if basisArg.Number < 0 || basisArg.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
settlementTime := timeFromExcelTime(settlement.Number, false)
maturityTime := timeFromExcelTime(maturity.Number, false)
basis := int(basisArg.Number)
numMonths := 12 / frequency.Number
fnArgs := list.New().Init()
fnArgs.PushBack(lastInterest)
fnArgs.PushBack(maturity)
fnArgs.PushBack(frequency)
fnArgs.PushBack(basisArg)
nc := fn.COUPNUM(fnArgs)
earlyCoupon := lastInterest.Number
aggrFunc := func(acc []float64, index float64) []float64 {
earlyCouponTime := timeFromExcelTime(earlyCoupon, false)
lateCouponTime := changeMonth(earlyCouponTime, numMonths, false)
lateCoupon, _ := timeToExcelTime(lateCouponTime, false)
nl := coupdays(earlyCouponTime, lateCouponTime, basis)
dci := coupdays(earlyCouponTime, maturityTime, basis)
if index < nc.Number {
dci = nl
}
var a float64
if lateCoupon < settlement.Number {
a = dci
} else if earlyCoupon < settlement.Number {
a = coupdays(earlyCouponTime, settlementTime, basis)
}
startDate := earlyCoupon
if settlement.Number > earlyCoupon {
startDate = settlement.Number
}
endDate := lateCoupon
if maturity.Number < lateCoupon {
endDate = maturity.Number
}
startDateTime := timeFromExcelTime(startDate, false)
endDateTime := timeFromExcelTime(endDate, false)
dsc := coupdays(startDateTime, endDateTime, basis)
earlyCoupon = lateCoupon
dcnl := acc[0]
anl := acc[1]
dscnl := acc[2]
return []float64{dcnl + dci/nl, anl + a/nl, dscnl + dsc/nl}
}
ag := aggrBetween(1, math.Floor(nc.Number), []float64{0, 0, 0}, aggrFunc)
dcnl, anl, dscnl := ag[0], ag[1], ag[2]
x := 100.0 * rate.Number / frequency.Number
term1 := dcnl*x + redemption.Number
if name == "ODDLPRICE" {
term2 := dscnl*prOrYld.Number/frequency.Number + 1
term3 := anl * x
return newNumberFormulaArg(term1/term2 - term3)
}
term2 := anl*x + prOrYld.Number
term3 := frequency.Number / dscnl
return newNumberFormulaArg((term1 - term2) / term2 * term3)
}
// ODDLPRICE function calculates the price per $100 face value of a security
// with an odd (short or long) last period. The syntax of the function is:
//
// ODDLPRICE(settlement,maturity,last_interest,rate,yld,redemption,frequency,[basis])
func (fn *formulaFuncs) ODDLPRICE(argsList *list.List) formulaArg {
return fn.oddl("ODDLPRICE", argsList)
}
// ODDLYIELD function calculates the yield of a security with an odd (short or
// long) last period. The syntax of the function is:
//
// ODDLYIELD(settlement,maturity,last_interest,rate,pr,redemption,frequency,[basis])
func (fn *formulaFuncs) ODDLYIELD(argsList *list.List) formulaArg {
return fn.oddl("ODDLYIELD", argsList)
}
// PDURATION function calculates the number of periods required for an
// investment to reach a specified future value. The syntax of the function
// is:
//
// PDURATION(rate,pv,fv)
func (fn *formulaFuncs) PDURATION(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "PDURATION requires 3 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
pv := argsList.Front().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
fv := argsList.Back().Value.(formulaArg).ToNumber()
if fv.Type != ArgNumber {
return fv
}
if rate.Number <= 0 || pv.Number <= 0 || fv.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg((math.Log(fv.Number) - math.Log(pv.Number)) / math.Log(1+rate.Number))
}
// PMT function calculates the constant periodic payment required to pay off
// (or partially pay off) a loan or investment, with a constant interest
// rate, over a specified period. The syntax of the function is:
//
// PMT(rate,nper,pv,[fv],[type])
func (fn *formulaFuncs) PMT(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "PMT requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "PMT allows at most 5 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
return fv
}
}
if argsList.Len() == 5 {
if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
return typ
}
}
if typ.Number != 0 && typ.Number != 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if rate.Number != 0 {
p := (-fv.Number - pv.Number*math.Pow(1+rate.Number, nper.Number)) / (1 + rate.Number*typ.Number) / ((math.Pow(1+rate.Number, nper.Number) - 1) / rate.Number)
return newNumberFormulaArg(p)
}
return newNumberFormulaArg((-pv.Number - fv.Number) / nper.Number)
}
// PPMT function calculates the payment on the principal, during a specific
// period of a loan or investment that is paid in constant periodic payments,
// with a constant interest rate. The syntax of the function is:
//
// PPMT(rate,per,nper,pv,[fv],[type])
func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg {
return fn.ipmt("PPMT", argsList)
}
// price is an implementation of the formula function PRICE.
func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequency, basis formulaArg) formulaArg {
if basis.Number < 0 || basis.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
argsList := list.New().Init()
argsList.PushBack(settlement)
argsList.PushBack(maturity)
argsList.PushBack(frequency)
argsList.PushBack(basis)
e := fn.COUPDAYS(argsList)
dsc := fn.COUPDAYSNC(argsList).Number / e.Number
n := fn.COUPNUM(argsList)
a := fn.COUPDAYBS(argsList)
ret := 0.0
if n.Number > 1 {
ret = redemption.Number / math.Pow(1+yld.Number/frequency.Number, n.Number-1+dsc)
ret -= 100 * rate.Number / frequency.Number * a.Number / e.Number
t1 := 100 * rate.Number / frequency.Number
t2 := 1 + yld.Number/frequency.Number
for k := 0.0; k < n.Number; k++ {
ret += t1 / math.Pow(t2, k+dsc)
}
} else {
dsc = e.Number - a.Number
t1 := 100*(rate.Number/frequency.Number) + redemption.Number
t2 := (yld.Number/frequency.Number)*(dsc/e.Number) + 1
t3 := 100 * (rate.Number / frequency.Number) * (a.Number / e.Number)
ret = t1/t2 - t3
}
return newNumberFormulaArg(ret)
}
// checkPriceYieldArgs checking and prepare arguments for the formula functions
// PRICE and YIELD.
func checkPriceYieldArgs(name string, rate, prYld, redemption, frequency formulaArg) formulaArg {
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
}
if prYld.Type != ArgNumber {
return prYld
}
if redemption.Type != ArgNumber {
return redemption
}
if name == "PRICE" {
if prYld.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0")
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0")
}
}
if name == "YIELD" {
if prYld.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELD requires pr > 0")
}
if redemption.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELD requires redemption >= 0")
}
}
if frequency.Type != ArgNumber {
return frequency
}
if !validateFrequency(frequency.Number) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newEmptyFormulaArg()
}
// priceYield is an implementation of the formula functions PRICE and YIELD.
func (fn *formulaFuncs) priceYield(name string, argsList *list.List) formulaArg {
if argsList.Len() != 6 && argsList.Len() != 7 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 or 7 arguments", name))
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
prYld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if arg := checkPriceYieldArgs(name, rate, prYld, redemption, frequency); arg.Type != ArgEmpty {
return arg
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 7 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
if name == "PRICE" {
return fn.price(settlement, maturity, rate, prYld, redemption, frequency, basis)
}
return fn.yield(settlement, maturity, rate, prYld, redemption, frequency, basis)
}
// PRICE function calculates the price, per $100 face value of a security that
// pays periodic interest. The syntax of the function is:
//
// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis])
func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
return fn.priceYield("PRICE", argsList)
}
// PRICEDISC function calculates the price, per $100 face value of a
// discounted security. The syntax of the function is:
//
// PRICEDISC(settlement,maturity,discount,redemption,[basis])
func (fn *formulaFuncs) PRICEDISC(argsList *list.List) formulaArg {
if argsList.Len() != 4 && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, "PRICEDISC requires 4 or 5 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
if maturity.Number <= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires maturity > settlement")
}
discount := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if discount.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if discount.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires discount > 0")
}
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if redemption.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires redemption > 0")
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
return newNumberFormulaArg(redemption.Number * (1 - discount.Number*frac.Number))
}
// PRICEMAT function calculates the price, per $100 face value of a security
// that pays interest at maturity. The syntax of the function is:
//
// PRICEMAT(settlement,maturity,issue,rate,yld,[basis])
func (fn *formulaFuncs) PRICEMAT(argsList *list.List) formulaArg {
if argsList.Len() != 5 && argsList.Len() != 6 {
return newErrorFormulaArg(formulaErrorVALUE, "PRICEMAT requires 5 or 6 arguments")
}
args := fn.prepareDataValueArgs(3, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity, issue := args.List[0], args.List[1], args.List[2]
if settlement.Number >= maturity.Number {
return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires maturity > settlement")
}
if issue.Number >= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires settlement > issue")
}
rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires rate >= 0")
}
yld := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if yld.Type != ArgNumber {
return yld
}
if yld.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires yld >= 0")
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 6 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if dsm.Type != ArgNumber {
return dsm
}
dis := yearFrac(issue.Number, settlement.Number, int(basis.Number))
dim := yearFrac(issue.Number, maturity.Number, int(basis.Number))
return newNumberFormulaArg(((1+dim.Number*rate.Number)/(1+dsm.Number*yld.Number) - dis.Number*rate.Number) * 100)
}
// PV function calculates the Present Value of an investment, based on a
// series of future payments. The syntax of the function is:
//
// PV(rate,nper,pmt,[fv],[type])
func (fn *formulaFuncs) PV(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "PV requires at least 3 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "PV allows at most 5 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pmt.Type != ArgNumber {
return pmt
}
fv := newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
return fv
}
}
t := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if t = argsList.Back().Value.(formulaArg).ToNumber(); t.Type != ArgNumber {
return t
}
if t.Number != 0 {
t.Number = 1
}
}
if rate.Number == 0 {
return newNumberFormulaArg(-pmt.Number*nper.Number - fv.Number)
}
return newNumberFormulaArg((((1-math.Pow(1+rate.Number, nper.Number))/rate.Number)*pmt.Number*(1+rate.Number*t.Number) - fv.Number) / math.Pow(1+rate.Number, nper.Number))
}
// rate is an implementation of the formula function RATE.
func (fn *formulaFuncs) rate(nper, pmt, pv, fv, t, guess formulaArg) formulaArg {
maxIter, iter, isClose, epsMax, rate := 100, 0, false, 1e-6, guess.Number
for iter < maxIter && !isClose {
t1 := math.Pow(rate+1, nper.Number)
t2 := math.Pow(rate+1, nper.Number-1)
rt := rate*t.Number + 1
p0 := pmt.Number * (t1 - 1)
f1 := fv.Number + t1*pv.Number + p0*rt/rate
n1 := nper.Number * t2 * pv.Number
n2 := p0 * rt / math.Pow(rate, 2)
f2 := math.Nextafter(n1, n1) - math.Nextafter(n2, n2)
f3 := (nper.Number*pmt.Number*t2*rt + p0*t.Number) / rate
delta := f1 / (f2 + f3)
if math.Abs(delta) < epsMax {
isClose = true
}
iter++
rate -= delta
}
return newNumberFormulaArg(rate)
}
// RATE function calculates the interest rate required to pay off a specified
// amount of a loan, or to reach a target amount on an investment, over a
// given period. The syntax of the function is:
//
// RATE(nper,pmt,pv,[fv],[type],[guess])
func (fn *formulaFuncs) RATE(argsList *list.List) formulaArg {
if argsList.Len() < 3 {
return newErrorFormulaArg(formulaErrorVALUE, "RATE requires at least 3 arguments")
}
if argsList.Len() > 6 {
return newErrorFormulaArg(formulaErrorVALUE, "RATE allows at most 6 arguments")
}
nper := argsList.Front().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber {
return nper
}
pmt := argsList.Front().Next().Value.(formulaArg).ToNumber()
if pmt.Type != ArgNumber {
return pmt
}
pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pv.Type != ArgNumber {
return pv
}
fv := newNumberFormulaArg(0)
if argsList.Len() >= 4 {
if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
return fv
}
}
t := newNumberFormulaArg(0)
if argsList.Len() >= 5 {
if t = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); t.Type != ArgNumber {
return t
}
if t.Number != 0 {
t.Number = 1
}
}
guess := newNumberFormulaArg(0.1)
if argsList.Len() == 6 {
if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber {
return guess
}
}
return fn.rate(nper, pmt, pv, fv, t, guess)
}
// RECEIVED function calculates the amount received at maturity for a fully
// invested security. The syntax of the function is:
//
// RECEIVED(settlement,maturity,investment,discount,[basis])
func (fn *formulaFuncs) RECEIVED(argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED requires at least 4 arguments")
}
if argsList.Len() > 5 {
return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED allows at most 5 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if investment.Type != ArgNumber {
return investment
}
discount := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if discount.Type != ArgNumber {
return discount
}
if discount.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "RECEIVED requires discount > 0")
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
return newNumberFormulaArg(investment.Number / (1 - discount.Number*frac.Number))
}
// RRI function calculates the equivalent interest rate for an investment with
// specified present value, future value and duration. The syntax of the
// function is:
//
// RRI(nper,pv,fv)
func (fn *formulaFuncs) RRI(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "RRI requires 3 arguments")
}
nper := argsList.Front().Value.(formulaArg).ToNumber()
pv := argsList.Front().Next().Value.(formulaArg).ToNumber()
fv := argsList.Back().Value.(formulaArg).ToNumber()
if nper.Type != ArgNumber || pv.Type != ArgNumber || fv.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if nper.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "RRI requires nper argument to be > 0")
}
if pv.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "RRI requires pv argument to be > 0")
}
if fv.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "RRI requires fv argument to be >= 0")
}
return newNumberFormulaArg(math.Pow(fv.Number/pv.Number, 1/nper.Number) - 1)
}
// SLN function calculates the straight line depreciation of an asset for one
// period. The syntax of the function is:
//
// SLN(cost,salvage,life)
func (fn *formulaFuncs) SLN(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "SLN requires 3 arguments")
}
cost := argsList.Front().Value.(formulaArg).ToNumber()
salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
life := argsList.Back().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if life.Number == 0 {
return newErrorFormulaArg(formulaErrorNUM, "SLN requires life argument to be > 0")
}
return newNumberFormulaArg((cost.Number - salvage.Number) / life.Number)
}
// SYD function calculates the sum-of-years' digits depreciation for a
// specified period in the lifetime of an asset. The syntax of the function
// is:
//
// SYD(cost,salvage,life,per)
func (fn *formulaFuncs) SYD(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "SYD requires 4 arguments")
}
cost := argsList.Front().Value.(formulaArg).ToNumber()
salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
life := argsList.Back().Prev().Value.(formulaArg).ToNumber()
per := argsList.Back().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber || per.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if life.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "SYD requires life argument to be > 0")
}
if per.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "SYD requires per argument to be > 0")
}
if per.Number > life.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(((cost.Number - salvage.Number) * (life.Number - per.Number + 1) * 2) / (life.Number * (life.Number + 1)))
}
// TBILLEQ function calculates the bond-equivalent yield for a Treasury Bill.
// The syntax of the function is:
//
// TBILLEQ(settlement,maturity,discount)
func (fn *formulaFuncs) TBILLEQ(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TBILLEQ requires 3 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
dsm := maturity.Number - settlement.Number
if dsm > 365 || maturity.Number <= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
discount := argsList.Back().Value.(formulaArg).ToNumber()
if discount.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if discount.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg((365 * discount.Number) / (360 - discount.Number*dsm))
}
// TBILLPRICE function returns the price, per $100 face value, of a Treasury
// Bill. The syntax of the function is:
//
// TBILLPRICE(settlement,maturity,discount)
func (fn *formulaFuncs) TBILLPRICE(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TBILLPRICE requires 3 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
dsm := maturity.Number - settlement.Number
if dsm > 365 || maturity.Number <= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
discount := argsList.Back().Value.(formulaArg).ToNumber()
if discount.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if discount.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(100 * (1 - discount.Number*dsm/360))
}
// TBILLYIELD function calculates the yield of a Treasury Bill. The syntax of
// the function is:
//
// TBILLYIELD(settlement,maturity,pr)
func (fn *formulaFuncs) TBILLYIELD(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "TBILLYIELD requires 3 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
dsm := maturity.Number - settlement.Number
if dsm > 365 || maturity.Number <= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
pr := argsList.Back().Value.(formulaArg).ToNumber()
if pr.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if pr.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(((100 - pr.Number) / pr.Number) * (360 / dsm))
}
// prepareVdbArgs checking and prepare arguments for the formula function
// VDB.
func (fn *formulaFuncs) prepareVdbArgs(argsList *list.List) formulaArg {
cost := argsList.Front().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber {
return cost
}
if cost.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires cost >= 0")
}
salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
if salvage.Type != ArgNumber {
return salvage
}
if salvage.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires salvage >= 0")
}
life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if life.Type != ArgNumber {
return life
}
if life.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires life > 0")
}
startPeriod := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if startPeriod.Type != ArgNumber {
return startPeriod
}
if startPeriod.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period > 0")
}
endPeriod := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if endPeriod.Type != ArgNumber {
return endPeriod
}
if startPeriod.Number > endPeriod.Number {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period <= end_period")
}
if endPeriod.Number > life.Number {
return newErrorFormulaArg(formulaErrorNUM, "VDB requires end_period <= life")
}
factor := newNumberFormulaArg(2)
if argsList.Len() > 5 {
if factor = argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if factor.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, "VDB requires factor >= 0")
}
}
return newListFormulaArg([]formulaArg{cost, salvage, life, startPeriod, endPeriod, factor})
}
// vdb is a part of implementation of the formula function VDB.
func (fn *formulaFuncs) vdb(cost, salvage, life, life1, period, factor formulaArg) formulaArg {
var ddb, vdb, sln, term float64
endInt, cs, nowSln := math.Ceil(period.Number), cost.Number-salvage.Number, false
ddbArgs := list.New()
for i := 1.0; i <= endInt; i++ {
if !nowSln {
ddbArgs.Init()
ddbArgs.PushBack(cost)
ddbArgs.PushBack(salvage)
ddbArgs.PushBack(life)
ddbArgs.PushBack(newNumberFormulaArg(i))
ddbArgs.PushBack(factor)
ddb = fn.DDB(ddbArgs).Number
sln = cs / (life1.Number - i + 1)
if sln > ddb && i != endInt {
term = sln
nowSln = true
} else {
term = ddb
cs -= ddb
}
} else {
term = sln
}
if i == endInt {
term *= period.Number + 1 - endInt
}
vdb += term
}
return newNumberFormulaArg(vdb)
}
// VDB function calculates the depreciation of an asset, using the Double
// Declining Balance Method, or another specified depreciation rate, for a
// specified period (including partial periods). The syntax of the function
// is:
//
// VDB(cost,salvage,life,start_period,end_period,[factor],[no_switch])
func (fn *formulaFuncs) VDB(argsList *list.List) formulaArg {
if argsList.Len() < 5 || argsList.Len() > 7 {
return newErrorFormulaArg(formulaErrorVALUE, "VDB requires 5 or 7 arguments")
}
args := fn.prepareVdbArgs(argsList)
if args.Type != ArgList {
return args
}
cost, salvage, life, startPeriod, endPeriod, factor := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]
noSwitch := newBoolFormulaArg(false)
if argsList.Len() > 6 {
if noSwitch = argsList.Back().Value.(formulaArg).ToBool(); noSwitch.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
startInt, endInt, vdb, ddbArgs := math.Floor(startPeriod.Number), math.Ceil(endPeriod.Number), newNumberFormulaArg(0), list.New()
if noSwitch.Number == 1 {
for i := startInt + 1; i <= endInt; i++ {
ddbArgs.Init()
ddbArgs.PushBack(cost)
ddbArgs.PushBack(salvage)
ddbArgs.PushBack(life)
ddbArgs.PushBack(newNumberFormulaArg(i))
ddbArgs.PushBack(factor)
term := fn.DDB(ddbArgs)
switch i {
case startInt + 1:
term.Number *= math.Min(endPeriod.Number, startInt+1) - startPeriod.Number
case endInt:
term.Number *= endPeriod.Number + 1 - endInt
}
vdb.Number += term.Number
}
return vdb
}
cost.Number -= fn.vdb(cost, salvage, life, life, startPeriod, factor).Number
return fn.vdb(cost, salvage, life, newNumberFormulaArg(life.Number-startPeriod.Number), newNumberFormulaArg(endPeriod.Number-startPeriod.Number), factor)
}
// prepareXArgs prepare arguments for the formula function XIRR and XNPV.
func (fn *formulaFuncs) prepareXArgs(values, dates formulaArg) (valuesArg, datesArg []float64, err formulaArg) {
for _, arg := range values.ToList() {
if numArg := arg.ToNumber(); numArg.Type == ArgNumber {
valuesArg = append(valuesArg, numArg.Number)
continue
}
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
if len(valuesArg) < 2 {
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
return
}
date := 0.0
for _, arg := range dates.ToList() {
if arg.Type == ArgNumber {
datesArg = append(datesArg, arg.Number)
if arg.Number < date {
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
date = arg.Number
continue
}
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
if len(valuesArg) != len(datesArg) {
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
return
}
err = newEmptyFormulaArg()
return
}
// xirr is an implementation of the formula function XIRR.
func (fn *formulaFuncs) xirr(values, dates []float64, guess float64) formulaArg {
positive, negative := false, false
for i := 0; i < len(values); i++ {
if values[i] > 0 {
positive = true
}
if values[i] < 0 {
negative = true
}
}
if !positive || !negative {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
result, epsMax, count, maxIterate, err := guess, 1e-10, 0, 50, false
for {
resultValue := xirrPart1(values, dates, result)
newRate := result - resultValue/xirrPart2(values, dates, result)
epsRate := math.Abs(newRate - result)
result = newRate
count++
if epsRate <= epsMax || math.Abs(resultValue) <= epsMax {
break
}
if count > maxIterate {
err = true
break
}
}
if err || math.IsNaN(result) || math.IsInf(result, 0) {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
return newNumberFormulaArg(result)
}
// xirrPart1 is a part of implementation of the formula function XIRR.
func xirrPart1(values, dates []float64, rate float64) float64 {
r := rate + 1
result := values[0]
vlen := len(values)
firstDate := dates[0]
for i := 1; i < vlen; i++ {
result += values[i] / math.Pow(r, (dates[i]-firstDate)/365)
}
return result
}
// xirrPart2 is a part of implementation of the formula function XIRR.
func xirrPart2(values, dates []float64, rate float64) float64 {
r := rate + 1
result := 0.0
vlen := len(values)
firstDate := dates[0]
for i := 1; i < vlen; i++ {
frac := (dates[i] - firstDate) / 365
result -= frac * values[i] / math.Pow(r, frac+1)
}
return result
}
// XIRR function returns the Internal Rate of Return for a supplied series of
// cash flows (i.e. a set of values, which includes an initial investment
// value and a series of net income values) occurring at a series of supplied
// dates. The syntax of the function is:
//
// XIRR(values,dates,[guess])
func (fn *formulaFuncs) XIRR(argsList *list.List) formulaArg {
if argsList.Len() != 2 && argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires 2 or 3 arguments")
}
values, dates, err := fn.prepareXArgs(argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg))
if err.Type != ArgEmpty {
return err
}
guess := newNumberFormulaArg(0)
if argsList.Len() == 3 {
if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if guess.Number <= -1 {
return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires guess > -1")
}
}
return fn.xirr(values, dates, guess.Number)
}
// XNPV function calculates the Net Present Value for a schedule of cash flows
// that is not necessarily periodic. The syntax of the function is:
//
// XNPV(rate,values,dates)
func (fn *formulaFuncs) XNPV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires 3 arguments")
}
rate := argsList.Front().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
if rate.Number <= 0 {
return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires rate > 0")
}
values, dates, err := fn.prepareXArgs(argsList.Front().Next().Value.(formulaArg), argsList.Back().Value.(formulaArg))
if err.Type != ArgEmpty {
return err
}
date1, xnpv := dates[0], 0.0
for idx, value := range values {
xnpv += value / math.Pow(1+rate.Number, (dates[idx]-date1)/365)
}
return newNumberFormulaArg(xnpv)
}
// yield is an implementation of the formula function YIELD.
func (fn *formulaFuncs) yield(settlement, maturity, rate, pr, redemption, frequency, basis formulaArg) formulaArg {
priceN, yield1, yield2 := newNumberFormulaArg(0), newNumberFormulaArg(0), newNumberFormulaArg(1)
price1 := fn.price(settlement, maturity, rate, yield1, redemption, frequency, basis)
if price1.Type != ArgNumber {
return price1
}
price2 := fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis)
yieldN := newNumberFormulaArg((yield2.Number - yield1.Number) * 0.5)
for iter := 0; iter < 100 && priceN.Number != pr.Number; iter++ {
priceN = fn.price(settlement, maturity, rate, yieldN, redemption, frequency, basis)
if pr.Number == price1.Number {
return yield1
} else if pr.Number == price2.Number {
return yield2
} else if pr.Number == priceN.Number {
return yieldN
} else if pr.Number < price2.Number {
yield2.Number *= 2.0
price2 = fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis)
yieldN.Number = (yield2.Number - yield1.Number) * 0.5
} else {
if pr.Number < priceN.Number {
yield1 = yieldN
price1 = priceN
} else {
yield2 = yieldN
price2 = priceN
}
f1 := (yield2.Number - yield1.Number) * ((pr.Number - price2.Number) / (price1.Number - price2.Number))
yieldN.Number = yield2.Number - math.Nextafter(f1, f1)
}
}
return yieldN
}
// YIELD function calculates the Yield of a security that pays periodic
// interest. The syntax of the function is:
//
// YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis])
func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg {
return fn.priceYield("YIELD", argsList)
}
// YIELDDISC function calculates the annual yield of a discounted security.
// The syntax of the function is:
//
// YIELDDISC(settlement,maturity,pr,redemption,[basis])
func (fn *formulaFuncs) YIELDDISC(argsList *list.List) formulaArg {
if argsList.Len() != 4 && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, "YIELDDISC requires 4 or 5 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if pr.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if pr.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires pr > 0")
}
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if redemption.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if redemption.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires redemption > 0")
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
return newNumberFormulaArg((redemption.Number/pr.Number - 1) / frac.Number)
}
// YIELDMAT function calculates the annual yield of a security that pays
// interest at maturity. The syntax of the function is:
//
// YIELDMAT(settlement,maturity,issue,rate,pr,[basis])
func (fn *formulaFuncs) YIELDMAT(argsList *list.List) formulaArg {
if argsList.Len() != 5 && argsList.Len() != 6 {
return newErrorFormulaArg(formulaErrorVALUE, "YIELDMAT requires 5 or 6 arguments")
}
args := fn.prepareDataValueArgs(2, argsList)
if args.Type != ArgList {
return args
}
settlement, maturity := args.List[0], args.List[1]
arg := list.New().Init()
issue := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if issue.Type != ArgNumber {
arg.PushBack(argsList.Front().Next().Next().Value.(formulaArg))
issue = fn.DATEVALUE(arg)
if issue.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
if issue.Number >= settlement.Number {
return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires settlement > issue")
}
rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires rate >= 0")
}
pr := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if pr.Type != ArgNumber {
return pr
}
if pr.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires pr > 0")
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 6 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
dim := yearFrac(issue.Number, maturity.Number, int(basis.Number))
if dim.Type != ArgNumber {
return dim
}
dis := yearFrac(issue.Number, settlement.Number, int(basis.Number))
dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
f1 := dim.Number * rate.Number
result := 1 + math.Nextafter(f1, f1)
result /= pr.Number/100 + dis.Number*rate.Number
result--
result /= dsm.Number
return newNumberFormulaArg(result)
}
// Database Functions
// calcDatabase defines the structure for formula database.
type calcDatabase struct {
col, row int
indexMap map[int]int
database [][]formulaArg
criteria [][]formulaArg
}
// newCalcDatabase function returns formula database by given data range of
// cells containing the database, field and criteria range.
func newCalcDatabase(database, field, criteria formulaArg) *calcDatabase {
db := calcDatabase{
indexMap: make(map[int]int),
database: database.Matrix,
criteria: criteria.Matrix,
}
exp := len(database.Matrix) < 2 || len(database.Matrix[0]) < 1 ||
len(criteria.Matrix) < 2 || len(criteria.Matrix[0]) < 1
if field.Type != ArgEmpty {
if db.col = db.columnIndex(database.Matrix, field); exp || db.col < 0 || len(db.database[0]) <= db.col {
return nil
}
return &db
}
if db.col = -1; exp {
return nil
}
return &db
}
// columnIndex return index by specifies column field within the database for
// which user want to return the count of non-blank cells.
func (db *calcDatabase) columnIndex(database [][]formulaArg, field formulaArg) int {
num := field.ToNumber()
if num.Type != ArgNumber && len(database) > 0 {
for i := 0; i < len(database[0]); i++ {
if title := database[0][i]; strings.EqualFold(title.Value(), field.Value()) {
return i
}
}
return -1
}
return int(num.Number - 1)
}
// criteriaEval evaluate formula criteria expression.
func (db *calcDatabase) criteriaEval() bool {
var (
columns, rows = len(db.criteria[0]), len(db.criteria)
criteria = db.criteria
k int
matched bool
)
if len(db.indexMap) == 0 {
fields := criteria[0]
for j := 0; j < columns; j++ {
if k = db.columnIndex(db.database, fields[j]); k < 0 {
return false
}
db.indexMap[j] = k
}
}
for i := 1; !matched && i < rows; i++ {
matched = true
for j := 0; matched && j < columns; j++ {
criteriaExp := db.criteria[i][j]
if criteriaExp.Value() == "" {
continue
}
criteria := formulaCriteriaParser(criteriaExp)
cell := db.database[db.row][db.indexMap[j]]
matched, _ = formulaCriteriaEval(cell, criteria)
}
}
return matched
}
// value returns the current cell value.
func (db *calcDatabase) value() formulaArg {
if db.col == -1 {
return db.database[db.row][len(db.database[db.row])-1]
}
return db.database[db.row][db.col]
}
// next will return true if find the matched cell in the database.
func (db *calcDatabase) next() bool {
matched, rows := false, len(db.database)
for !matched && db.row < rows {
if db.row++; db.row < rows {
matched = db.criteriaEval()
}
}
return matched
}
// database is an implementation of the formula functions DAVERAGE, DMAX and DMIN.
func (fn *formulaFuncs) database(name string, argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name))
}
database := argsList.Front().Value.(formulaArg)
field := argsList.Front().Next().Value.(formulaArg)
criteria := argsList.Back().Value.(formulaArg)
db := newCalcDatabase(database, field, criteria)
if db == nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args := list.New()
for db.next() {
args.PushBack(db.value())
}
switch name {
case "DMAX":
return fn.MAX(args)
case "DMIN":
return fn.MIN(args)
case "DPRODUCT":
return fn.PRODUCT(args)
case "DSTDEV":
return fn.STDEV(args)
case "DSTDEVP":
return fn.STDEVP(args)
case "DSUM":
return fn.SUM(args)
case "DVAR":
return fn.VAR(args)
case "DVARP":
return fn.VARP(args)
default:
return fn.AVERAGE(args)
}
}
// DAVERAGE function calculates the average (statistical mean) of values in a
// field (column) in a database for selected records, that satisfy
// user-specified criteria. The syntax of the function is:
//
// DAVERAGE(database,field,criteria)
func (fn *formulaFuncs) DAVERAGE(argsList *list.List) formulaArg {
return fn.database("DAVERAGE", argsList)
}
// dcount is an implementation of the formula functions DCOUNT and DCOUNTA.
func (fn *formulaFuncs) dcount(name string, argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name))
}
field := newEmptyFormulaArg()
criteria := argsList.Back().Value.(formulaArg)
if argsList.Len() > 2 {
field = argsList.Front().Next().Value.(formulaArg)
}
database := argsList.Front().Value.(formulaArg)
db := newCalcDatabase(database, field, criteria)
if db == nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args := list.New()
for db.next() {
args.PushBack(db.value())
}
if name == "DCOUNT" {
return fn.COUNT(args)
}
return fn.COUNTA(args)
}
// DCOUNT function returns the number of cells containing numeric values, in a
// field (column) of a database for selected records only. The records to be
// included in the count are those that satisfy a set of one or more
// user-specified criteria. The syntax of the function is:
//
// DCOUNT(database,[field],criteria)
func (fn *formulaFuncs) DCOUNT(argsList *list.List) formulaArg {
return fn.dcount("DCOUNT", argsList)
}
// DCOUNTA function returns the number of non-blank cells, in a field
// (column) of a database for selected records only. The records to be
// included in the count are those that satisfy a set of one or more
// user-specified criteria. The syntax of the function is:
//
// DCOUNTA(database,[field],criteria)
func (fn *formulaFuncs) DCOUNTA(argsList *list.List) formulaArg {
return fn.dcount("DCOUNTA", argsList)
}
// DGET function returns a single value from a column of a database. The record
// is selected via a set of one or more user-specified criteria. The syntax of
// the function is:
//
// DGET(database,field,criteria)
func (fn *formulaFuncs) DGET(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "DGET requires 3 arguments")
}
database := argsList.Front().Value.(formulaArg)
field := argsList.Front().Next().Value.(formulaArg)
criteria := argsList.Back().Value.(formulaArg)
db := newCalcDatabase(database, field, criteria)
if db == nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
value := newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
if db.next() {
if value = db.value(); db.next() {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return value
}
// DMAX function finds the maximum value in a field (column) in a database for
// selected records only. The records to be included in the calculation are
// defined by a set of one or more user-specified criteria. The syntax of the
// function is:
//
// DMAX(database,field,criteria)
func (fn *formulaFuncs) DMAX(argsList *list.List) formulaArg {
return fn.database("DMAX", argsList)
}
// DMIN function finds the minimum value in a field (column) in a database for
// selected records only. The records to be included in the calculation are
// defined by a set of one or more user-specified criteria. The syntax of the
// function is:
//
// DMIN(database,field,criteria)
func (fn *formulaFuncs) DMIN(argsList *list.List) formulaArg {
return fn.database("DMIN", argsList)
}
// DPRODUCT function calculates the product of a field (column) in a database
// for selected records, that satisfy user-specified criteria. The syntax of
// the function is:
//
// DPRODUCT(database,field,criteria)
func (fn *formulaFuncs) DPRODUCT(argsList *list.List) formulaArg {
return fn.database("DPRODUCT", argsList)
}
// DSTDEV function calculates the sample standard deviation of a field
// (column) in a database for selected records only. The records to be
// included in the calculation are defined by a set of one or more
// user-specified criteria. The syntax of the function is:
//
// DSTDEV(database,field,criteria)
func (fn *formulaFuncs) DSTDEV(argsList *list.List) formulaArg {
return fn.database("DSTDEV", argsList)
}
// DSTDEVP function calculates the standard deviation of a field (column) in a
// database for selected records only. The records to be included in the
// calculation are defined by a set of one or more user-specified criteria.
// The syntax of the function is:
//
// DSTDEVP(database,field,criteria)
func (fn *formulaFuncs) DSTDEVP(argsList *list.List) formulaArg {
return fn.database("DSTDEVP", argsList)
}
// DSUM function calculates the sum of a field (column) in a database for
// selected records, that satisfy user-specified criteria. The syntax of the
// function is:
//
// DSUM(database,field,criteria)
func (fn *formulaFuncs) DSUM(argsList *list.List) formulaArg {
return fn.database("DSUM", argsList)
}
// DVAR function calculates the sample variance of a field (column) in a
// database for selected records only. The records to be included in the
// calculation are defined by a set of one or more user-specified criteria.
// The syntax of the function is:
//
// DVAR(database,field,criteria)
func (fn *formulaFuncs) DVAR(argsList *list.List) formulaArg {
return fn.database("DVAR", argsList)
}
// DVARP function calculates the variance (for an entire population), of the
// values in a field (column) in a database for selected records only. The
// records to be included in the calculation are defined by a set of one or
// more user-specified criteria. The syntax of the function is:
//
// DVARP(database,field,criteria)
func (fn *formulaFuncs) DVARP(argsList *list.List) formulaArg {
return fn.database("DVARP", argsList)
}
// DISPIMG function calculates the Kingsoft WPS Office embedded image ID. The
// syntax of the function is:
//
// DISPIMG(picture_name,display_mode)
func (fn *formulaFuncs) DISPIMG(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DISPIMG requires 2 numeric arguments")
}
return argsList.Front().Value.(formulaArg)
}
// sortbyArgs holds the parsed arguments for the SORTBY function.
type sortbyArgs struct {
array []formulaArg
cols int
rows int
sortKeys []sortbyKey
}
// sortbyKey represents a single sort key with its associated array and order.
type sortbyKey struct {
byArray []formulaArg
cols int
rows int
ascending bool // true = ascending (1 or omitted), false = descending (-1)
}
// rowWithKeys pairs a data row with its corresponding sort key values.
type rowWithKeys struct {
rowData []formulaArg
sortKeys [][]formulaArg // up to 3 sets of sort keys
}
// SORTBY function sorts the contents of a range or array based on the values
// in a corresponding range or array. The syntax of the function is:
//
// SORTBY(array,by_array1,[sort_order1],[by_array2,sort_order2],[by_array3, sort_order3])
func (fn *formulaFuncs) SORTBY(argsList *list.List) formulaArg {
args, errArg := prepareSortbyArgs(argsList)
if errArg != nil {
return *errArg
}
rowsWithKeys := make([]rowWithKeys, args.rows)
for i := 0; i < args.rows; i++ {
rowsWithKeys[i].rowData = args.array[i*args.cols : (i+1)*args.cols]
rowsWithKeys[i].sortKeys = make([][]formulaArg, len(args.sortKeys))
for keyIdx, sortKey := range args.sortKeys {
rowsWithKeys[i].sortKeys[keyIdx] = sortKey.byArray[i*sortKey.cols : (i+1)*sortKey.cols]
}
}
sort.Slice(rowsWithKeys, func(i, j int) bool {
return compareRowsForSortby(rowsWithKeys[i], rowsWithKeys[j], args.sortKeys)
})
result := make([][]formulaArg, args.rows)
for i, row := range rowsWithKeys {
result[i] = row.rowData
}
return newMatrixFormulaArg(result)
}
// checkSortbyArgs checking arguments for the formula function SORTBY.
func checkSortbyArgs(argsList *list.List) formulaArg {
argsLen := argsList.Len()
if argsLen < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "SORTBY requires at least 2 arguments")
}
if argsLen > 7 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("SORTBY takes at most 7 arguments, received %d", argsLen))
}
if argsLen != 2 && argsLen != 3 && argsLen != 5 && argsLen != 7 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("SORTBY requires 2, 3, 5, or 7 arguments, received %d", argsLen))
}
arrArg := argsList.Front().Value.(formulaArg).ToList()
if len(arrArg) == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "missing first argument to SORTBY")
}
if arrArg[0].Type == ArgError {
return arrArg[0]
}
return newListFormulaArg(arrArg)
}
// prepareSortbyArgs prepare arguments for the formula function SORTBY.
func prepareSortbyArgs(argsList *list.List) (sortbyArgs, *formulaArg) {
res := sortbyArgs{}
args := checkSortbyArgs(argsList)
if args.Type != ArgList {
return res, &args
}
res.array = args.List
argsLen := argsList.Len()
array := argsList.Front()
tempList := list.New()
tempList.PushBack(array.Value)
rmin, rmax := calcColsRowsMinMax(false, tempList)
cmin, cmax := calcColsRowsMinMax(true, tempList)
res.cols, res.rows = cmax-cmin+1, rmax-rmin+1
byArray := array.Next()
keyCount := 0
for byArray != nil && keyCount < 3 {
var key sortbyKey
key.byArray = byArray.Value.(formulaArg).ToList()
if len(key.byArray) == 0 {
errArg := newErrorFormulaArg(formulaErrorVALUE, "missing by_array argument to SORTBY")
return res, &errArg
}
if key.byArray[0].Type == ArgError {
return res, &key.byArray[0]
}
tempList := list.New()
tempList.PushBack(byArray.Value)
rmin, rmax := calcColsRowsMinMax(false, tempList)
cmin, cmax := calcColsRowsMinMax(true, tempList)
key.cols, key.rows = cmax-cmin+1, rmax-rmin+1
if key.rows != res.rows {
errArg := newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("by_array dimensions (%d rows) do not match array dimensions (%d rows)",
key.rows, res.rows))
return res, &errArg
}
key.ascending = true
nextByArray, errArg := parseSortOrderArg(byArray.Next(), &key, keyCount, argsLen)
if errArg != nil {
return res, errArg
}
byArray = nextByArray
res.sortKeys = append(res.sortKeys, key)
keyCount++
}
return res, nil
}
// parseSortOrderArg processes the optional sort_order argument for a SORTBY
// key. It updates the key's ascending field and returns the next element in the
// list. Returns an error if the sort_order value is invalid.
func parseSortOrderArg(byArray *list.Element, key *sortbyKey, keyCount, argsLen int) (*list.Element, *formulaArg) {
if byArray == nil {
return nil, nil
}
sortOrderArg := byArray.Value.(formulaArg).ToNumber()
expectedSortOrder := false
if keyCount == 0 && (argsLen == 3 || argsLen == 5 || argsLen == 7) {
expectedSortOrder = true
} else if keyCount == 1 && (argsLen == 5 || argsLen == 7) {
expectedSortOrder = true
} else if keyCount == 2 && argsLen == 7 {
expectedSortOrder = true
}
if sortOrderArg.Type == ArgError {
if expectedSortOrder {
return byArray, &sortOrderArg
}
return byArray, nil
}
switch sortOrderArg.Number {
case -1:
key.ascending = false
return byArray.Next(), nil
case 1:
key.ascending = true
return byArray.Next(), nil
}
errArg := newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("sort_order must be 1 or -1, received %v", sortOrderArg.Number))
return byArray, &errArg
}
// compareRowsForSortby compares two rows using multiple sort keys.
// Returns true if row i should come before row j.
func compareRowsForSortby(i, j rowWithKeys, sortKeys []sortbyKey) bool {
for idx, sortKey := range sortKeys {
lhs, rhs := i.sortKeys[idx], j.sortKeys[idx]
for colIdx := 0; colIdx < len(lhs) && colIdx < len(rhs); colIdx++ {
criteria := compareFormulaArg(lhs[colIdx], rhs[colIdx], newNumberFormulaArg(matchModeMaxLess), false)
if criteria == criteriaEq {
continue
}
if sortKey.ascending {
return criteria == criteriaL
}
return criteria == criteriaG
}
}
return false
}
================================================
FILE: calc_test.go
================================================
package excelize
import (
"container/list"
"math"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/xuri/efp"
)
func prepareCalcData(cellData [][]interface{}) *File {
f := NewFile()
for r, row := range cellData {
for c, value := range row {
cell, _ := CoordinatesToCellName(c+1, r+1)
_ = f.SetCellValue("Sheet1", cell, value)
}
}
return f
}
func TestCalcCellValue(t *testing.T) {
cellData := [][]interface{}{
{1, 4, nil, "Month", "Team", "Sales"},
{2, 5, nil, "Jan", "North 1", 36693},
{3, nil, nil, "Jan", "North 2", 22100},
{0, nil, nil, "Jan", "South 1", 53321},
{nil, nil, nil, "Jan", "South 2", 34440},
{nil, nil, nil, "Feb", "North 1", 29889},
{nil, nil, nil, "Feb", "North 2", 50090},
{nil, nil, nil, "Feb", "South 1", 32080},
{nil, nil, nil, "Feb", "South 2", 45500},
}
mathCalc := map[string]string{
"2^3": "8",
"1=1": "TRUE",
"1=2": "FALSE",
"1<2": "TRUE",
"3<2": "FALSE",
"1<\"-1\"": "TRUE",
"\"-1\"<1": "FALSE",
"\"-1\"<\"-2\"": "TRUE",
"2<=3": "TRUE",
"2<=1": "FALSE",
"1<=\"-1\"": "TRUE",
"\"-1\"<=1": "FALSE",
"\"-1\"<=\"-2\"": "TRUE",
"2>1": "TRUE",
"2>3": "FALSE",
"1>\"-1\"": "FALSE",
"\"-1\">-1": "TRUE",
"\"-1\">\"-2\"": "FALSE",
"2>=1": "TRUE",
"2>=3": "FALSE",
"1>=\"-1\"": "FALSE",
"\"-1\">=-1": "TRUE",
"\"-1\">=\"-2\"": "FALSE",
"-----1+1": "0",
"------1+1": "2",
"---1---1": "-2",
"---1----1": "0",
"1&2": "12",
"15%": "0.15",
"1+20%": "1.2",
"{1}+2": "3",
"1+{2}": "3",
"{1}+{2}": "3",
"A1+(B1-C1)": "5",
"A1+(C1-B1)": "-3",
"A1&B1&C1": "14",
"B1+C1": "4",
"C1+B1": "4",
"C1+C1": "0",
"\"A\"=\"A\"": "TRUE",
"\"A\"<>\"A\"": "FALSE",
"TRUE()&FALSE()": "TRUEFALSE",
"TRUE()&FALSE()<>FALSE": "TRUE",
"TRUE()&\"1\"": "TRUE1",
"TRUE<>FALSE()": "TRUE",
"TRUE<>1&\"x\"": "TRUE",
// Engineering Functions
// BESSELI
"BESSELI(4.5,1)": "15.3892227537359",
"BESSELI(32,1)": "5502845511211.25",
"BESSELI({32},1)": "5502845511211.25",
"BESSELI(32,{1})": "5502845511211.25",
"BESSELI({32},{1})": "5502845511211.25",
// BESSELJ
"BESSELJ(1.9,2)": "0.329925727692387",
"BESSELJ({1.9},2)": "0.329925727692387",
"BESSELJ(1.9,{2})": "0.329925727692387",
"BESSELJ({1.9},{2})": "0.329925727692387",
// BESSELK
"BESSELK(0.05,0)": "3.11423403428966",
"BESSELK(0.05,1)": "19.9096743272486",
"BESSELK(0.05,2)": "799.501207124235",
"BESSELK(3,2)": "0.0615104585619118",
"BESSELK({3},2)": "0.0615104585619118",
"BESSELK(3,{2})": "0.0615104585619118",
"BESSELK({3},{2})": "0.0615104585619118",
// BESSELY
"BESSELY(0.05,0)": "-1.97931100684153",
"BESSELY(0.05,1)": "-12.789855163794",
"BESSELY(0.05,2)": "-509.61489554492",
"BESSELY(9,2)": "-0.229082087487741",
"BESSELY({9},2)": "-0.229082087487741",
"BESSELY(9,{2})": "-0.229082087487741",
"BESSELY({9},{2})": "-0.229082087487741",
// BIN2DEC
"BIN2DEC(\"10\")": "2",
"BIN2DEC(\"11\")": "3",
"BIN2DEC(\"0000000010\")": "2",
"BIN2DEC(\"1111111110\")": "-2",
"BIN2DEC(\"110\")": "6",
"BIN2DEC({\"110\"})": "6",
// BIN2HEX
"BIN2HEX(\"10\")": "2",
"BIN2HEX(\"0000000001\")": "1",
"BIN2HEX(\"10\",10)": "0000000002",
"BIN2HEX(\"1111111110\")": "FFFFFFFFFE",
"BIN2HEX(\"11101\")": "1D",
"BIN2HEX({\"11101\"})": "1D",
// BIN2OCT
"BIN2OCT(\"101\")": "5",
"BIN2OCT(\"0000000001\")": "1",
"BIN2OCT(\"10\",10)": "0000000002",
"BIN2OCT(\"1111111110\")": "7777777776",
"BIN2OCT(\"1110\")": "16",
"BIN2OCT({\"1110\"})": "16",
// BITAND
"BITAND(13,14)": "12",
"BITAND({13},14)": "12",
"BITAND(13,{14})": "12",
"BITAND({13},{14})": "12",
// BITLSHIFT
"BITLSHIFT(5,2)": "20",
"BITLSHIFT({3},5)": "96",
"BITLSHIFT(3,5)": "96",
"BITLSHIFT(3,{5})": "96",
"BITLSHIFT({3},{5})": "96",
// BITOR
"BITOR(9,12)": "13",
"BITOR({9},12)": "13",
"BITOR(9,{12})": "13",
"BITOR({9},{12})": "13",
// BITRSHIFT
"BITRSHIFT(20,2)": "5",
"BITRSHIFT(52,4)": "3",
"BITRSHIFT({52},4)": "3",
"BITRSHIFT(52,{4})": "3",
"BITRSHIFT({52},{4})": "3",
// BITXOR
"BITXOR(5,6)": "3",
"BITXOR(9,12)": "5",
"BITXOR({9},12)": "5",
"BITXOR(9,{12})": "5",
"BITXOR({9},{12})": "5",
// COMPLEX
"COMPLEX(5,2)": "5+2i",
"COMPLEX(5,-9)": "5-9i",
"COMPLEX(-1,2,\"j\")": "-1+2j",
"COMPLEX(10,-5,\"i\")": "10-5i",
"COMPLEX(0,5)": "5i",
"COMPLEX(3,0)": "3",
"COMPLEX(0,-2)": "-2i",
"COMPLEX(0,0)": "0",
"COMPLEX(0,-1,\"j\")": "-j",
// CONVERT
"CONVERT(20.2,\"m\",\"yd\")": "22.0909886264217",
"CONVERT(20.2,\"cm\",\"yd\")": "0.220909886264217",
"CONVERT(0.2,\"gal\",\"tsp\")": "153.6",
"CONVERT(5,\"gal\",\"l\")": "18.92705892",
"CONVERT(0.02,\"Gm\",\"m\")": "20000000",
"CONVERT(0,\"C\",\"F\")": "32",
"CONVERT(1,\"ly^2\",\"ly^2\")": "1",
"CONVERT(0.00194255938572296,\"sg\",\"ozm\")": "1",
"CONVERT(5,\"kg\",\"kg\")": "5",
"CONVERT(4.5359237E-01,\"kg\",\"lbm\")": "1",
"CONVERT(0.2,\"kg\",\"hg\")": "2",
"CONVERT(12.345000000000001,\"km\",\"m\")": "12345",
"CONVERT(12345,\"m\",\"km\")": "12.345",
"CONVERT(0.621371192237334,\"mi\",\"km\")": "1",
"CONVERT(1.23450000000000E+05,\"ang\",\"um\")": "12.345",
"CONVERT(1.23450000000000E+02,\"kang\",\"um\")": "12.345",
"CONVERT(1000,\"dal\",\"hl\")": "100",
"CONVERT(1,\"yd\",\"ft\")": "2.99999999999999",
"CONVERT(20,\"C\",\"F\")": "68",
"CONVERT(68,\"F\",\"C\")": "20",
"CONVERT(293.15,\"K\",\"F\")": "68",
"CONVERT(68,\"F\",\"K\")": "293.15",
"CONVERT(-273.15,\"C\",\"K\")": "0",
"CONVERT(-459.67,\"F\",\"K\")": "0",
"CONVERT(295.65,\"K\",\"C\")": "22.5",
"CONVERT(22.5,\"C\",\"K\")": "295.65",
"CONVERT(1667.85,\"C\",\"K\")": "1941",
"CONVERT(3034.13,\"F\",\"K\")": "1941",
"CONVERT(3493.8,\"Rank\",\"K\")": "1941",
"CONVERT(1334.28,\"Reau\",\"K\")": "1941",
"CONVERT(1941,\"K\",\"Rank\")": "3493.8",
"CONVERT(1941,\"K\",\"Reau\")": "1334.28",
"CONVERT(123.45,\"K\",\"kel\")": "123.45",
"CONVERT(123.45,\"C\",\"cel\")": "123.45",
"CONVERT(123.45,\"F\",\"fah\")": "123.45",
"CONVERT(16,\"bit\",\"byte\")": "2",
"CONVERT(1,\"kbyte\",\"byte\")": "1000",
"CONVERT(1,\"kibyte\",\"byte\")": "1024",
// DEC2BIN
"DEC2BIN(2)": "10",
"DEC2BIN(3)": "11",
"DEC2BIN(2,10)": "0000000010",
"DEC2BIN(-2)": "1111111110",
"DEC2BIN(6)": "110",
// DEC2HEX
"DEC2HEX(10)": "A",
"DEC2HEX(31)": "1F",
"DEC2HEX(16,10)": "0000000010",
"DEC2HEX(-16)": "FFFFFFFFF0",
"DEC2HEX(273)": "111",
// DEC2OCT
"DEC2OCT(8)": "10",
"DEC2OCT(18)": "22",
"DEC2OCT(8,10)": "0000000010",
"DEC2OCT(-8)": "7777777770",
"DEC2OCT(237)": "355",
// DELTA
"DELTA(5,4)": "0",
"DELTA(1.00001,1)": "0",
"DELTA(1.23,1.23)": "1",
"DELTA(1)": "0",
"DELTA(0)": "1",
// ERF
"ERF(1.5)": "0.966105146475311",
"ERF(0,1.5)": "0.966105146475311",
"ERF(1,2)": "0.152621472069238",
// ERF.PRECISE
"ERF.PRECISE(-1)": "-0.842700792949715",
"ERF.PRECISE(1.5)": "0.966105146475311",
// ERFC
"ERFC(0)": "1",
"ERFC(0.5)": "0.479500122186953",
"ERFC(-1)": "1.84270079294971",
// ERFC.PRECISE
"ERFC.PRECISE(0)": "1",
"ERFC.PRECISE(0.5)": "0.479500122186953",
"ERFC.PRECISE(-1)": "1.84270079294971",
// GESTEP
"GESTEP(1.2,0.001)": "1",
"GESTEP(0.05,0.05)": "1",
"GESTEP(-0.00001,0)": "0",
"GESTEP(-0.00001)": "0",
// HEX2BIN
"HEX2BIN(\"2\")": "10",
"HEX2BIN(\"0000000001\")": "1",
"HEX2BIN(\"2\",10)": "0000000010",
"HEX2BIN(\"F0\")": "11110000",
"HEX2BIN(\"1D\")": "11101",
// HEX2DEC
"HEX2DEC(\"A\")": "10",
"HEX2DEC(\"1F\")": "31",
"HEX2DEC(\"0000000010\")": "16",
"HEX2DEC(\"FFFFFFFFF0\")": "-16",
"HEX2DEC(\"111\")": "273",
"HEX2DEC(\"\")": "0",
// HEX2OCT
"HEX2OCT(\"A\")": "12",
"HEX2OCT(\"000000000F\")": "17",
"HEX2OCT(\"8\",10)": "0000000010",
"HEX2OCT(\"FFFFFFFFF8\")": "7777777770",
"HEX2OCT(\"1F3\")": "763",
"HEX2OCT({\"1F3\"})": "763",
// IMABS
"IMABS(\"2j\")": "2",
"IMABS(\"-1+2i\")": "2.23606797749979",
"IMABS(COMPLEX(-1,2,\"j\"))": "2.23606797749979",
// IMAGINARY
"IMAGINARY(\"5+2i\")": "2",
"IMAGINARY(\"2-i\")": "-1",
"IMAGINARY(6)": "0",
"IMAGINARY(\"3i\")": "3",
"IMAGINARY(\"4+i\")": "1",
// IMARGUMENT
"IMARGUMENT(\"5+2i\")": "0.380506377112365",
"IMARGUMENT(\"2-i\")": "-0.463647609000806",
"IMARGUMENT(6)": "0",
// IMCONJUGATE
"IMCONJUGATE(\"5+2i\")": "5-2i",
"IMCONJUGATE(\"2-i\")": "2+i",
"IMCONJUGATE(6)": "6",
"IMCONJUGATE(\"3i\")": "-3i",
"IMCONJUGATE(\"4+i\")": "4-i",
// IMCOS
"IMCOS(0)": "1",
"IMCOS(0.5)": "0.877582561890373",
"IMCOS(\"3+0.5i\")": "-1.11634124452615-0.0735369737112366i",
// IMCOSH
"IMCOSH(0.5)": "1.12762596520638",
"IMCOSH(\"3+0.5i\")": "8.83520460650099+4.80282508274303i",
"IMCOSH(\"2-i\")": "2.03272300701967-3.0518977991518i",
"IMCOSH(COMPLEX(1,-1))": "0.833730025131149-0.988897705762865i",
// IMCOT
"IMCOT(0.5)": "1.83048772171245",
"IMCOT(\"3+0.5i\")": "-0.479345578747373-2.01609252150623i",
"IMCOT(\"2-i\")": "-0.171383612909185+0.821329797493852i",
"IMCOT(COMPLEX(1,-1))": "0.217621561854403+0.868014142895925i",
// IMCSC
"IMCSC(\"j\")": "-0.850918128239322j",
// IMCSCH
"IMCSCH(COMPLEX(1,-1))": "0.303931001628426+0.621518017170428i",
// IMDIV
"IMDIV(\"5+2i\",\"1+i\")": "3.5-1.5i",
"IMDIV(\"2+2i\",\"2+i\")": "1.2+0.4i",
"IMDIV(COMPLEX(5,2),COMPLEX(0,1))": "2-5i",
// IMEXP
"IMEXP(0)": "1",
"IMEXP(0.5)": "1.64872127070013",
"IMEXP(\"1-2i\")": "-1.13120438375681-2.47172667200482i",
"IMEXP(COMPLEX(1,-1))": "1.46869393991589-2.28735528717884i",
// IMLN
"IMLN(0.5)": "-0.693147180559945",
"IMLN(\"3+0.5i\")": "1.11231177576217+0.165148677414627i",
"IMLN(\"2-i\")": "0.80471895621705-0.463647609000806i",
"IMLN(COMPLEX(1,-1))": "0.346573590279973-0.785398163397448i",
// IMLOG10
"IMLOG10(0.5)": "-0.301029995663981",
"IMLOG10(\"3+0.5i\")": "0.483070866369516+0.0717231592947926i",
"IMLOG10(\"2-i\")": "0.349485002168009-0.201359598136687i",
"IMLOG10(COMPLEX(1,-1))": "0.150514997831991-0.34109408846046i",
// IMREAL
"IMREAL(\"5+2i\")": "5",
"IMREAL(\"2+2i\")": "2",
"IMREAL(6)": "6",
"IMREAL(\"3i\")": "0",
"IMREAL(COMPLEX(4,1))": "4",
// IMSEC
"IMSEC(0.5)": "1.13949392732455",
"IMSEC(\"3+0.5i\")": "-0.89191317974033+0.0587531781817398i",
"IMSEC(\"2-i\")": "-0.41314934426694-0.687527438655479i",
"IMSEC(COMPLEX(1,-1))": "0.498337030555187-0.591083841721045i",
// IMSECH
"IMSECH(0.5)": "0.886818883970074",
"IMSECH(\"3+0.5i\")": "0.0873665779621303-0.0474925494901607i",
"IMSECH(\"2-i\")": "0.151176298265577+0.226973675393722i",
"IMSECH(COMPLEX(1,-1))": "0.498337030555187+0.591083841721045i",
// IMSIN
"IMSIN(0.5)": "0.479425538604203",
"IMSIN(\"3+0.5i\")": "0.15913058529844-0.515880442452527i",
"IMSIN(\"2-i\")": "1.40311925062204+0.489056259041294i",
"IMSIN(COMPLEX(1,-1))": "1.29845758141598-0.634963914784736i",
// IMSINH
"IMSINH(-0)": "0",
"IMSINH(0.5)": "0.521095305493747",
"IMSINH(\"3+0.5i\")": "8.79151234349371+4.82669427481082i",
"IMSINH(\"2-i\")": "1.95960104142161-3.16577851321617i",
"IMSINH(COMPLEX(1,-1))": "0.634963914784736-1.29845758141598i",
// IMSQRT
"IMSQRT(\"i\")": "0.707106781186548+0.707106781186548i",
"IMSQRT(\"2-i\")": "1.45534669022535-0.343560749722512i",
"IMSQRT(\"5+2i\")": "2.27872385417085+0.438842116902254i",
"IMSQRT(6)": "2.44948974278318",
"IMSQRT(\"-2-4i\")": "1.11178594050284-1.79890743994787i",
// IMSUB
"IMSUB(\"5+i\",\"1+4i\")": "4-3i",
"IMSUB(\"9+2i\",6)": "3+2i",
"IMSUB(COMPLEX(5,2),COMPLEX(0,1))": "5+i",
// IMSUM
"IMSUM(\"1-i\",\"5+10i\",2)": "8+9i",
"IMSUM(COMPLEX(5,2),COMPLEX(0,1))": "5+3i",
// IMTAN
"IMTAN(-0)": "0",
"IMTAN(0.5)": "0.54630248984379",
"IMTAN(\"3+0.5i\")": "-0.111621050771583+0.469469993425885i",
"IMTAN(\"2-i\")": "-0.243458201185725-1.16673625724092i",
"IMTAN(COMPLEX(1,-1))": "0.271752585319512-1.08392332733869i",
// OCT2BIN
"OCT2BIN(\"5\")": "101",
"OCT2BIN(\"0000000001\")": "1",
"OCT2BIN(\"2\",10)": "0000000010",
"OCT2BIN(\"7777777770\")": "1111111000",
"OCT2BIN(\"16\")": "1110",
// OCT2DEC
"OCT2DEC(\"10\")": "8",
"OCT2DEC(\"22\")": "18",
"OCT2DEC(\"0000000010\")": "8",
"OCT2DEC(\"7777777770\")": "-8",
"OCT2DEC(\"355\")": "237",
// OCT2HEX
"OCT2HEX(\"10\")": "8",
"OCT2HEX(\"0000000007\")": "7",
"OCT2HEX(\"10\",10)": "0000000008",
"OCT2HEX(\"7777777770\")": "FFFFFFFFF8",
"OCT2HEX(\"763\")": "1F3",
// Math and Trigonometric Functions
// ABS
"ABS(-1)": "1",
"ABS(-6.5)": "6.5",
"ABS(6.5)": "6.5",
"ABS(0)": "0",
"ABS(2-4.5)": "2.5",
"ABS(ABS(-1))": "1",
// ACOS
"ACOS(-1)": "3.14159265358979",
"ACOS(0)": "1.5707963267949",
"ACOS(ABS(0))": "1.5707963267949",
// ACOSH
"ACOSH(1)": "0",
"ACOSH(2.5)": "1.56679923697241",
"ACOSH(5)": "2.29243166956118",
"ACOSH(ACOSH(5))": "1.47138332153668",
// _xlfn.ACOT
"_xlfn.ACOT(1)": "0.785398163397448",
"_xlfn.ACOT(-2)": "2.67794504458899",
"_xlfn.ACOT(0)": "1.5707963267949",
"_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009",
// _xlfn.ACOTH
"_xlfn.ACOTH(-5)": "-0.202732554054082",
"_xlfn.ACOTH(1.1)": "1.52226121886171",
"_xlfn.ACOTH(2)": "0.549306144334055",
"_xlfn.ACOTH(ABS(-2))": "0.549306144334055",
// _xlfn.AGGREGATE
"_xlfn.AGGREGATE(1,0,A1:A6)": "1.5",
"_xlfn.AGGREGATE(2,0,A1:A6)": "4",
"_xlfn.AGGREGATE(3,0,A1:A6)": "4",
"_xlfn.AGGREGATE(4,0,A1:A6)": "3",
"_xlfn.AGGREGATE(5,0,A1:A6)": "0",
"_xlfn.AGGREGATE(6,0,A1:A6)": "0",
"_xlfn.AGGREGATE(7,0,A1:A6)": "1.29099444873581",
"_xlfn.AGGREGATE(8,0,A1:A6)": "1.11803398874989",
"_xlfn.AGGREGATE(9,0,A1:A6)": "6",
"_xlfn.AGGREGATE(10,0,A1:A6)": "1.66666666666667",
"_xlfn.AGGREGATE(11,0,A1:A6)": "1.25",
"_xlfn.AGGREGATE(12,0,A1:A6)": "1.5",
"_xlfn.AGGREGATE(14,0,A1:A6,1)": "3",
"_xlfn.AGGREGATE(15,0,A1:A6,1)": "0",
"_xlfn.AGGREGATE(16,0,A1:A6,1)": "3",
"_xlfn.AGGREGATE(17,0,A1:A6,1)": "0.75",
"_xlfn.AGGREGATE(19,0,A1:A6,1)": "0.25",
// ARABIC
"_xlfn.ARABIC(\"IV\")": "4",
"_xlfn.ARABIC(\"-IV\")": "-4",
"_xlfn.ARABIC(\"MCXX\")": "1120",
"_xlfn.ARABIC(\"\")": "0",
"_xlfn.ARABIC(\" ll lc \")": "-50",
// ASIN
"ASIN(-1)": "-1.5707963267949",
"ASIN(0)": "0",
"ASIN(ASIN(0))": "0",
// ASINH
"ASINH(0)": "0",
"ASINH(-0.5)": "-0.481211825059603",
"ASINH(2)": "1.44363547517881",
"ASINH(ASINH(0))": "0",
// ATAN
"ATAN(-1)": "-0.785398163397448",
"ATAN(0)": "0",
"ATAN(1)": "0.785398163397448",
"ATAN(ATAN(0))": "0",
// ATANH
"ATANH(-0.8)": "-1.09861228866811",
"ATANH(0)": "0",
"ATANH(0.5)": "0.549306144334055",
"ATANH(ATANH(0))": "0",
// ATAN2
"ATAN2(1,1)": "0.785398163397448",
"ATAN2(1,-1)": "-0.785398163397448",
"ATAN2(4,0)": "0",
"ATAN2(4,ATAN2(4,0))": "0",
// BASE
"BASE(12,2)": "1100",
"BASE(12,2,8)": "00001100",
"BASE(100000,16)": "186A0",
"BASE(BASE(12,2),16)": "44C",
// CEILING
"CEILING(22.25,0.1)": "22.3",
"CEILING(22.25,0.5)": "22.5",
"CEILING(22.25,1)": "23",
"CEILING(22.25,10)": "30",
"CEILING(22.25,20)": "40",
"CEILING(-22.25,-0.1)": "-22.3",
"CEILING(-22.25,-1)": "-23",
"CEILING(-22.25,-5)": "-25",
"CEILING(22.25)": "23",
"CEILING(CEILING(22.25,0.1),0.1)": "22.3",
// _xlfn.CEILING.MATH
"_xlfn.CEILING.MATH(15.25,1)": "16",
"_xlfn.CEILING.MATH(15.25,0.1)": "15.3",
"_xlfn.CEILING.MATH(15.25,5)": "20",
"_xlfn.CEILING.MATH(-15.25,1)": "-15",
"_xlfn.CEILING.MATH(-15.25,1,1)": "-15", // should be 16
"_xlfn.CEILING.MATH(-15.25,10)": "-10",
"_xlfn.CEILING.MATH(-15.25)": "-15",
"_xlfn.CEILING.MATH(-15.25,-5,-1)": "-10",
"_xlfn.CEILING.MATH(_xlfn.CEILING.MATH(15.25,1),1)": "16",
// _xlfn.CEILING.PRECISE
"_xlfn.CEILING.PRECISE(22.25,0.1)": "22.3",
"_xlfn.CEILING.PRECISE(22.25,0.5)": "22.5",
"_xlfn.CEILING.PRECISE(22.25,1)": "23",
"_xlfn.CEILING.PRECISE(22.25)": "23",
"_xlfn.CEILING.PRECISE(22.25,10)": "30",
"_xlfn.CEILING.PRECISE(22.25,0)": "0",
"_xlfn.CEILING.PRECISE(-22.25,1)": "-22",
"_xlfn.CEILING.PRECISE(-22.25,-1)": "-22",
"_xlfn.CEILING.PRECISE(-22.25,5)": "-20",
"_xlfn.CEILING.PRECISE(_xlfn.CEILING.PRECISE(22.25,0.1),5)": "25",
// COMBIN
"COMBIN(6,1)": "6",
"COMBIN(6,2)": "15",
"COMBIN(6,3)": "20",
"COMBIN(6,4)": "15",
"COMBIN(6,5)": "6",
"COMBIN(6,6)": "1",
"COMBIN(0,0)": "1",
"COMBIN(6,COMBIN(0,0))": "6",
// _xlfn.COMBINA
"_xlfn.COMBINA(6,1)": "6",
"_xlfn.COMBINA(6,2)": "21",
"_xlfn.COMBINA(6,3)": "56",
"_xlfn.COMBINA(6,4)": "126",
"_xlfn.COMBINA(6,5)": "252",
"_xlfn.COMBINA(6,6)": "462",
"_xlfn.COMBINA(0,0)": "0",
"_xlfn.COMBINA(0,_xlfn.COMBINA(0,0))": "0",
// COS
"COS(0.785398163)": "0.707106781467586",
"COS(0)": "1",
"-COS(0)": "-1",
"COS(COS(0))": "0.54030230586814",
// COSH
"COSH(0)": "1",
"COSH(0.5)": "1.12762596520638",
"COSH(-2)": "3.76219569108363",
"COSH(COSH(0))": "1.54308063481524",
// _xlfn.COT
"_xlfn.COT(0.785398163397448)": "1",
"_xlfn.COT(_xlfn.COT(0.45))": "-0.545473116787229",
// _xlfn.COTH
"_xlfn.COTH(-3.14159265358979)": "-1.00374187319732",
"_xlfn.COTH(_xlfn.COTH(1))": "1.15601401811395",
// _xlfn.CSC
"_xlfn.CSC(-6)": "3.57889954725441",
"_xlfn.CSC(1.5707963267949)": "1",
"_xlfn.CSC(_xlfn.CSC(1))": "1.07785184031088",
// _xlfn.CSCH
"_xlfn.CSCH(-3.14159265358979)": "-0.0865895375300472",
"_xlfn.CSCH(_xlfn.CSCH(1))": "1.04451010395518",
// _xlfn.DECIMAL
"_xlfn.DECIMAL(\"1100\",2)": "12",
"_xlfn.DECIMAL(\"186A0\",16)": "100000",
"_xlfn.DECIMAL(\"31L0\",32)": "100000",
"_xlfn.DECIMAL(\"70122\",8)": "28754",
"_xlfn.DECIMAL(\"0x70122\",8)": "28754",
// DEGREES
"DEGREES(1)": "57.2957795130823",
"DEGREES(2.5)": "143.239448782706",
"DEGREES(DEGREES(1))": "3282.80635001174",
// EVEN
"EVEN(23)": "24",
"EVEN(2.22)": "4",
"EVEN(0)": "0",
"EVEN(-0.3)": "-2",
"EVEN(-11)": "-12",
"EVEN(-4)": "-4",
"EVEN((0))": "0",
// EXP
"EXP(100)": "2.68811714181614E+43",
"EXP(0.1)": "1.10517091807565",
"EXP(0)": "1",
"EXP(-5)": "0.00673794699908547",
"EXP(EXP(0))": "2.71828182845905",
// FACT
"FACT(3)": "6",
"FACT(6)": "720",
"FACT(10)": "3628800",
"FACT(FACT(3))": "720",
// FACTDOUBLE
"FACTDOUBLE(5)": "15",
"FACTDOUBLE(8)": "384",
"FACTDOUBLE(13)": "135135",
"FACTDOUBLE(FACTDOUBLE(1))": "1",
// FLOOR
"FLOOR(26.75,0.1)": "26.7",
"FLOOR(26.75,0.5)": "26.5",
"FLOOR(26.75,1)": "26",
"FLOOR(26.75,10)": "20",
"FLOOR(26.75,20)": "20",
"FLOOR(-26.75,-0.1)": "-26.7",
"FLOOR(-26.75,-1)": "-26",
"FLOOR(-26.75,-5)": "-25",
"FLOOR(-2.05,2)": "-4",
"FLOOR(FLOOR(26.75,1),1)": "26",
// _xlfn.FLOOR.MATH
"_xlfn.FLOOR.MATH(58.55)": "58",
"_xlfn.FLOOR.MATH(58.55,0.1)": "58.5",
"_xlfn.FLOOR.MATH(58.55,5)": "55",
"_xlfn.FLOOR.MATH(58.55,1,1)": "58",
"_xlfn.FLOOR.MATH(-58.55,1)": "-59",
"_xlfn.FLOOR.MATH(-58.55,1,-1)": "-58",
"_xlfn.FLOOR.MATH(-58.55,1,1)": "-59", // should be -58
"_xlfn.FLOOR.MATH(-58.55,10)": "-60",
"_xlfn.FLOOR.MATH(_xlfn.FLOOR.MATH(1),10)": "0",
// _xlfn.FLOOR.PRECISE
"_xlfn.FLOOR.PRECISE(26.75,0.1)": "26.7",
"_xlfn.FLOOR.PRECISE(26.75,0.5)": "26.5",
"_xlfn.FLOOR.PRECISE(26.75,1)": "26",
"_xlfn.FLOOR.PRECISE(26.75)": "26",
"_xlfn.FLOOR.PRECISE(26.75,10)": "20",
"_xlfn.FLOOR.PRECISE(26.75,0)": "0",
"_xlfn.FLOOR.PRECISE(-26.75,1)": "-27",
"_xlfn.FLOOR.PRECISE(-26.75,-1)": "-27",
"_xlfn.FLOOR.PRECISE(-26.75,-5)": "-30",
"_xlfn.FLOOR.PRECISE(_xlfn.FLOOR.PRECISE(26.75),-5)": "25",
// GCD
"GCD(0)": "0",
"GCD(1,0)": "1",
"GCD(\"0\",1)": "1",
"GCD(1,5)": "1",
"GCD(15,10,25)": "5",
"GCD(0,8,12)": "4",
"GCD(7,2)": "1",
"GCD(1,GCD(1))": "1",
// INT
"INT(100.9)": "100",
"INT(5.22)": "5",
"INT(5.99)": "5",
"INT(-6.1)": "-7",
"INT(-100.9)": "-101",
"INT(INT(0))": "0",
// ISO.CEILING
"ISO.CEILING(22.25)": "23",
"ISO.CEILING(22.25,1)": "23",
"ISO.CEILING(22.25,0.1)": "22.3",
"ISO.CEILING(22.25,10)": "30",
"ISO.CEILING(-22.25,1)": "-22",
"ISO.CEILING(-22.25,0.1)": "-22.2",
"ISO.CEILING(-22.25,5)": "-20",
"ISO.CEILING(-22.25,0)": "0",
"ISO.CEILING(1,ISO.CEILING(1,0))": "0",
// LCM
"LCM(1,5)": "5",
"LCM(15,10,25)": "150",
"LCM(1,8,12)": "24",
"LCM(7,2)": "14",
"LCM(7)": "7",
"LCM(\"\",1)": "1",
"LCM(0,0)": "0",
"LCM(0,LCM(0,0))": "0",
// LN
"LN(1)": "0",
"LN(100)": "4.60517018598809",
"LN(0.5)": "-0.693147180559945",
"LN(LN(100))": "1.5271796258079",
// LOG
"LOG(64,2)": "6",
"LOG(100)": "2",
"LOG(4,0.5)": "-2",
"LOG(500)": "2.69897000433602",
"LOG(LOG(100))": "0.301029995663981",
// LOG10
"LOG10(100)": "2",
"LOG10(1000)": "3",
"LOG10(0.001)": "-3",
"LOG10(25)": "1.39794000867204",
"LOG10(LOG10(100))": "0.301029995663981",
// IMLOG2
"IMLOG2(\"5+2i\")": "2.42899049756379+0.548954663286635i",
"IMLOG2(\"2-i\")": "1.16096404744368-0.668902106225488i",
"IMLOG2(6)": "2.58496250072116",
"IMLOG2(\"3i\")": "1.58496250072116+2.2661800709136i",
"IMLOG2(\"4+i\")": "2.04373142062517+0.353429502416735i",
// IMPOWER
"IMPOWER(\"2-i\",2)": "3-4i",
"IMPOWER(\"2-i\",3)": "2-11i",
"IMPOWER(9,0.5)": "3",
"IMPOWER(\"2+4i\",-2)": "-0.03-0.04i",
// IMPRODUCT
"IMPRODUCT(3,6)": "18",
"IMPRODUCT(\"\",3,SUM(6))": "18",
"IMPRODUCT(\"1-i\",\"5+10i\",2)": "30+10i",
"IMPRODUCT(COMPLEX(5,2),COMPLEX(0,1))": "-2+5i",
"IMPRODUCT(A1:C1)": "4",
// MINVERSE
"MINVERSE(A1:B2)": "-0",
// MMULT
"MMULT(0,0)": "0",
"MMULT(2,4)": "8",
"MMULT(A4:A4,A4:A4)": "0",
// MOD
"MOD(6,4)": "2",
"MOD(6,3)": "0",
"MOD(6,2.5)": "1",
"MOD(6,1.333)": "0.668",
"MOD(-10.23,1)": "0.77",
"MOD(MOD(1,1),1)": "0",
// MROUND
"MROUND(333.7,0.5)": "333.5",
"MROUND(333.8,1)": "334",
"MROUND(333.3,2)": "334",
"MROUND(555.3,400)": "400",
"MROUND(555,1000)": "1000",
"MROUND(-555.7,-1)": "-556",
"MROUND(-555.4,-1)": "-555",
"MROUND(-1555,-1000)": "-2000",
"MROUND(MROUND(1,1),1)": "1",
// MULTINOMIAL
"MULTINOMIAL(3,1,2,5)": "27720",
"MULTINOMIAL(\"\",3,1,2,5)": "27720",
"MULTINOMIAL(MULTINOMIAL(1))": "1",
// _xlfn.MUNIT
"_xlfn.MUNIT(4)": "1",
// ODD
"ODD(22)": "23",
"ODD(1.22)": "3",
"ODD(1.22+4)": "7",
"ODD(0)": "1",
"ODD(-1.3)": "-3",
"ODD(-10)": "-11",
"ODD(-3)": "-3",
"ODD(ODD(1))": "1",
// PI
"PI()": "3.14159265358979",
// POWER
"POWER(4,2)": "16",
"POWER(4,POWER(1,1))": "4",
// PRODUCT
"PRODUCT(3,6)": "18",
"PRODUCT(\"3\",\"6\")": "18",
"PRODUCT(PRODUCT(1),3,6)": "18",
"PRODUCT(C1:C2)": "1",
// QUOTIENT
"QUOTIENT(5,2)": "2",
"QUOTIENT(4.5,3.1)": "1",
"QUOTIENT(-10,3)": "-3",
"QUOTIENT(QUOTIENT(1,2),3)": "0",
// RADIANS
"RADIANS(50)": "0.872664625997165",
"RADIANS(-180)": "-3.14159265358979",
"RADIANS(180)": "3.14159265358979",
"RADIANS(360)": "6.28318530717959",
"RADIANS(RADIANS(360))": "0.109662271123215",
// ROMAN
"ROMAN(499,0)": "CDXCIX",
"ROMAN(1999,0)": "MCMXCIX",
"ROMAN(1999,1)": "MLMVLIV",
"ROMAN(1999,2)": "MXMIX",
"ROMAN(1999,3)": "MVMIV",
"ROMAN(1999,4)": "MIM",
"ROMAN(1999,-1)": "MCMXCIX",
"ROMAN(1999,5)": "MIM",
"ROMAN(1999,ODD(1))": "MLMVLIV",
// ROUND
"ROUND(100.319,1)": "100.3",
"ROUND(5.28,1)": "5.3",
"ROUND(5.9999,3)": "6",
"ROUND(99.5,0)": "100",
"ROUND(-6.3,0)": "-6",
"ROUND(-100.5,0)": "-101",
"ROUND(-22.45,1)": "-22.5",
"ROUND(999,-1)": "1000",
"ROUND(991,-1)": "990",
"ROUND(ROUND(100,1),-1)": "100",
// ROUNDDOWN
"ROUNDDOWN(99.999,1)": "99.9",
"ROUNDDOWN(99.999,2)": "99.99",
"ROUNDDOWN(99.999,0)": "99",
"ROUNDDOWN(99.999,-1)": "90",
"ROUNDDOWN(-99.999,2)": "-99.99",
"ROUNDDOWN(-99.999,-1)": "-90",
"ROUNDDOWN(ROUNDDOWN(100,1),-1)": "100",
// ROUNDUP
"ROUNDUP(11.111,1)": "11.2",
"ROUNDUP(11.111,2)": "11.12",
"ROUNDUP(11.111,0)": "12",
"ROUNDUP(11.111,-1)": "20",
"ROUNDUP(-11.111,2)": "-11.12",
"ROUNDUP(-11.111,-1)": "-20",
"ROUNDUP(ROUNDUP(100,1),-1)": "100",
// SEARCH
"SEARCH(\"s\",F1)": "1",
"SEARCH(\"s\",F1,2)": "5",
"SEARCH(\"e\",F1)": "4",
"SEARCH(\"e*\",F1)": "4",
"SEARCH(\"?e\",F1)": "3",
"SEARCH(\"??e\",F1)": "2",
"SEARCH(6,F2)": "2",
"SEARCH(\"?\",\"你好world\")": "1",
"SEARCH(\"?l\",\"你好world\")": "5",
"SEARCH(\"?+\",\"你好 1+2\")": "4",
"SEARCH(\" ?+\",\"你好 1+2\")": "3",
// SEARCHB
"SEARCHB(\"s\",F1)": "1",
"SEARCHB(\"s\",F1,2)": "5",
"SEARCHB(\"e\",F1)": "4",
"SEARCHB(\"e*\",F1)": "4",
"SEARCHB(\"?e\",F1)": "3",
"SEARCHB(\"??e\",F1)": "2",
"SEARCHB(6,F2)": "2",
"SEARCHB(\"?\",\"你好world\")": "5",
"SEARCHB(\"?l\",\"你好world\")": "7",
"SEARCHB(\"?+\",\"你好 1+2\")": "6",
"SEARCHB(\" ?+\",\"你好 1+2\")": "5",
// SEC
"_xlfn.SEC(-3.14159265358979)": "-1",
"_xlfn.SEC(0)": "1",
"_xlfn.SEC(_xlfn.SEC(0))": "0.54030230586814",
// SECH
"_xlfn.SECH(-3.14159265358979)": "0.0862667383340547",
"_xlfn.SECH(0)": "1",
"_xlfn.SECH(_xlfn.SECH(0))": "0.648054273663885",
// SERIESSUM
"SERIESSUM(1,2,3,A1:A4)": "6",
"SERIESSUM(1,2,3,A1:B5)": "15",
// SIGN
"SIGN(9.5)": "1",
"SIGN(-9.5)": "-1",
"SIGN(0)": "0",
"SIGN(0.00000001)": "1",
"SIGN(6-7)": "-1",
"SIGN(SIGN(-1))": "-1",
// SIN
"SIN(0.785398163)": "0.707106780905509",
"SIN(SIN(1))": "0.745624141665558",
// SINH
"SINH(0)": "0",
"SINH(0.5)": "0.521095305493747",
"SINH(-2)": "-3.62686040784702",
"SINH(SINH(0))": "0",
// SQRT
"SQRT(4)": "2",
"SQRT(SQRT(16))": "2",
// SQRTPI
"SQRTPI(5)": "3.96332729760601",
"SQRTPI(0.2)": "0.792665459521202",
"SQRTPI(100)": "17.7245385090552",
"SQRTPI(0)": "0",
"SQRTPI(SQRTPI(0))": "0",
// STDEV
"STDEV(F2:F9)": "10724.9782875238",
"STDEV(MUNIT(2))": "0.577350269189626",
"STDEV(0,INT(0))": "0",
"STDEV(INT(1),INT(1))": "0",
// STDEV.S
"STDEV.S(F2:F9)": "10724.9782875238",
// STDEVA
"STDEVA(F2:F9)": "10724.9782875238",
"STDEVA(MUNIT(2))": "0.577350269189626",
"STDEVA(0,INT(0))": "0",
// POISSON.DIST
"POISSON.DIST(20,25,FALSE)": "0.0519174686084913",
"POISSON.DIST(35,40,TRUE)": "0.242414197690103",
// POISSON
"POISSON(20,25,FALSE)": "0.0519174686084913",
"POISSON(35,40,TRUE)": "0.242414197690103",
// SUBTOTAL
"SUBTOTAL(1,A1:A6)": "1.5",
"SUBTOTAL(2,A1:A6)": "4",
"SUBTOTAL(3,A1:A6)": "4",
"SUBTOTAL(4,A1:A6)": "3",
"SUBTOTAL(5,A1:A6)": "0",
"SUBTOTAL(6,A1:A6)": "0",
"SUBTOTAL(7,A1:A6)": "1.29099444873581",
"SUBTOTAL(8,A1:A6)": "1.11803398874989",
"SUBTOTAL(9,A1:A6)": "6",
"SUBTOTAL(10,A1:A6)": "1.66666666666667",
"SUBTOTAL(11,A1:A6)": "1.25",
"SUBTOTAL(101,A1:A6)": "1.5",
"SUBTOTAL(102,A1:A6)": "4",
"SUBTOTAL(103,A1:A6)": "4",
"SUBTOTAL(104,A1:A6)": "3",
"SUBTOTAL(105,A1:A6)": "0",
"SUBTOTAL(106,A1:A6)": "0",
"SUBTOTAL(107,A1:A6)": "1.29099444873581",
"SUBTOTAL(108,A1:A6)": "1.11803398874989",
"SUBTOTAL(109,A1:A6)": "6",
"SUBTOTAL(109,A1:A6,A1:A6)": "12",
"SUBTOTAL(110,A1:A6)": "1.66666666666667",
"SUBTOTAL(111,A1:A6)": "1.25",
"SUBTOTAL(111,A1:A6,A1:A6)": "1.25",
// SUM
"SUM(1,2)": "3",
"SUM(\"1\",\"2\")": "3",
"SUM(\"\",1,2)": "3",
"SUM(1,2+3)": "6",
"SUM(SUM(1,2),2)": "5",
"(-2-SUM(-4+7))*5": "-25",
"SUM(1,2,3,4,5,6,7)": "28",
"SUM(1,2)+SUM(1,2)": "6",
"1+SUM(SUM(1,2*3),4)": "12",
"1+SUM(SUM(1,-2*3),4)": "0",
"(-2-SUM(-4*(7+7)))*5": "270",
"SUM(SUM(1+2/1)*2-3/2,2)": "6.5",
"((3+5*2)+3)/5+(-6)/4*2+3": "3.2",
"1+SUM(SUM(1,2*3),4)*-4/2+5+(4+2)*3": "2",
"1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.6666666666667",
"SUM(1+ROW())": "2",
"SUM((SUM(2))+1)": "3",
"IF(2<0, 1, (4))": "4",
"IF(2>0, (1), 4)": "1",
"IF(2>0, (A1)*2.5, 4)": "2.5",
"SUM({1,2,3,4,\"\"})": "10",
// SUMIF
"SUMIF(F1:F5, \"\")": "0",
"SUMIF(A1:A5, \"3\")": "3",
"SUMIF(F1:F5, \"=36693\")": "36693",
"SUMIF(F1:F5, \"<100\")": "0",
"SUMIF(F1:F5, \"<=36693\")": "93233",
"SUMIF(F1:F5, \">100\")": "146554",
"SUMIF(F1:F5, \">=100\")": "146554",
"SUMIF(F1:F5, \">=text\")": "0",
"SUMIF(F1:F5, \"*Jan\",F2:F5)": "0",
"SUMIF(D3:D7,\"Jan\",F2:F5)": "112114",
"SUMIF(D2:D9,\"Feb\",F2:F9)": "157559",
"SUMIF(E2:E9,\"North 1\",F2:F9)": "66582",
"SUMIF(E2:E9,\"North*\",F2:F9)": "138772",
"SUMIF(D1:D3,\"Month\",D1:D3)": "0",
// SUMPRODUCT
"SUMPRODUCT(A1,B1)": "4",
"SUMPRODUCT(A1:A2,B1:B2)": "14",
"SUMPRODUCT(A1:A3,B1:B3)": "14",
"SUMPRODUCT(A1:B3)": "15",
"SUMPRODUCT(A1:A3,B1:B3,B2:B4)": "20",
// SUMSQ
"SUMSQ(A1:A4)": "14",
"SUMSQ(A1,B1,A2,B2,6)": "82",
"SUMSQ(\"\",A1,B1,A2,B2,6)": "82",
"SUMSQ(1,SUMSQ(1))": "2",
"SUMSQ(\"1\",SUMSQ(1))": "2",
"SUMSQ(MUNIT(3))": "3",
// SUMX2MY2
"SUMX2MY2(A1:A4,B1:B4)": "-36",
// SUMX2PY2
"SUMX2PY2(A1:A4,B1:B4)": "46",
// SUMXMY2
"SUMXMY2(A1:A4,B1:B4)": "18",
// TAN
"TAN(1.047197551)": "1.73205080678249",
"TAN(0)": "0",
"TAN(TAN(0))": "0",
// TANH
"TANH(0)": "0",
"TANH(0.5)": "0.46211715726001",
"TANH(-2)": "-0.964027580075817",
"TANH(TANH(0))": "0",
// TRUNC
"TRUNC(99.999,1)": "99.9",
"TRUNC(99.999,2)": "99.99",
"TRUNC(99.999)": "99",
"TRUNC(99.999,-1)": "90",
"TRUNC(-99.999,2)": "-99.99",
"TRUNC(-99.999,-1)": "-90",
"TRUNC(TRUNC(1),-1)": "0",
// Statistical Functions
// AVEDEV
"AVEDEV(1,2)": "0.5",
"AVERAGE(A1:A4,B1:B4)": "2.5",
// AVERAGE
"AVERAGE(INT(1))": "1",
"AVERAGE(A1)": "1",
"AVERAGE(A1:A2)": "1.5",
"AVERAGE(D2:F9)": "38014.125",
// AVERAGEA
"AVERAGEA(INT(1))": "1",
"AVERAGEA(A1)": "1",
"AVERAGEA(\"1\")": "1",
"AVERAGEA(A1:A2)": "1.5",
"AVERAGEA(D2:F9)": "12671.375",
// BETA.DIST
"BETA.DIST(0.4,4,5,TRUE,0,1)": "0.4059136",
"BETA.DIST(0.6,4,5,FALSE,0,1)": "1.548288",
// BETADIST
"BETADIST(0.4,4,5)": "0.4059136",
"BETADIST(0.4,4,5,0,1)": "0.4059136",
"BETADIST(0.4,4,5,0.4,1)": "0",
"BETADIST(1,2,2,1,3)": "0",
"BETADIST(0.4,4,5,0.2,0.4)": "1",
"BETADIST(0.4,4,1)": "0.0256",
"BETADIST(0.4,1,5)": "0.92224",
"BETADIST(3,4,6,2,4)": "0.74609375",
"BETADIST(0.4,2,100)": "1",
"BETADIST(0.75,3,4)": "0.96240234375",
"BETADIST(0.2,0.7,4)": "0.71794309318323",
"BETADIST(0.01,3,4)": "1.955359E-05",
"BETADIST(0.75,130,140)": "1",
// BETAINV
"BETAINV(0.2,4,5,0,1)": "0.303225844664082",
// BETA.INV
"BETA.INV(0.2,4,5,0,1)": "0.303225844664082",
// BINOMDIST
"BINOMDIST(10,100,0.5,FALSE)": "1.36554263874631E-17",
"BINOMDIST(50,100,0.5,FALSE)": "0.0795892373871787",
"BINOMDIST(65,100,0.5,FALSE)": "0.000863855665741652",
"BINOMDIST(10,100,0.5,TRUE)": "1.53164508771899E-17",
"BINOMDIST(50,100,0.5,TRUE)": "0.539794618693589",
"BINOMDIST(65,100,0.5,TRUE)": "0.999105034804256",
// BINOM.DIST
"BINOM.DIST(10,100,0.5,FALSE)": "1.36554263874631E-17",
"BINOM.DIST(50,100,0.5,FALSE)": "0.0795892373871787",
"BINOM.DIST(65,100,0.5,FALSE)": "0.000863855665741652",
"BINOM.DIST(10,100,0.5,TRUE)": "1.53164508771899E-17",
"BINOM.DIST(50,100,0.5,TRUE)": "0.539794618693589",
"BINOM.DIST(65,100,0.5,TRUE)": "0.999105034804256",
// BINOM.DIST.RANGE
"BINOM.DIST.RANGE(100,0.5,0,40)": "0.0284439668204904",
"BINOM.DIST.RANGE(100,0.5,45,55)": "0.728746975926165",
"BINOM.DIST.RANGE(100,0.5,50,100)": "0.539794618693589",
"BINOM.DIST.RANGE(100,0.5,50)": "0.0795892373871787",
// BINOM.INV
"BINOM.INV(0,0.5,0.75)": "0",
"BINOM.INV(0.1,0.1,0.75)": "0",
"BINOM.INV(0.6,0.4,0.75)": "0",
"BINOM.INV(2,0.4,0.75)": "1",
"BINOM.INV(100,0.5,20%)": "46",
"BINOM.INV(100,0.5,50%)": "50",
"BINOM.INV(100,0.5,90%)": "56",
// CHIDIST
"CHIDIST(0.5,3)": "0.918891411654676",
"CHIDIST(8,3)": "0.0460117056892314",
"CHIDIST(40,4)": "4.32842260712097E-08",
"CHIDIST(42,4)": "1.66816329414062E-08",
// CHIINV
"CHIINV(0.5,1)": "0.454936423119572",
"CHIINV(0.75,1)": "0.101531044267622",
"CHIINV(0.1,2)": "4.60517018598809",
"CHIINV(0.8,2)": "0.446287102628419",
// CHISQ.DIST
"CHISQ.DIST(0,2,TRUE)": "0",
"CHISQ.DIST(4,1,TRUE)": "0.954499736103642",
"CHISQ.DIST(1180,1180,FALSE)": "0.00821093706387967",
"CHISQ.DIST(2,1,FALSE)": "0.103776874355149",
"CHISQ.DIST(3,2,FALSE)": "0.111565080074215",
"CHISQ.DIST(2,3,FALSE)": "0.207553748710297",
"CHISQ.DIST(1425,1,FALSE)": "3.88315098887099E-312",
"CHISQ.DIST(3,2,TRUE)": "0.77686983985157",
// CHISQ.DIST.RT
"CHISQ.DIST.RT(0.5,3)": "0.918891411654676",
"CHISQ.DIST.RT(8,3)": "0.0460117056892314",
"CHISQ.DIST.RT(40,4)": "4.32842260712097E-08",
"CHISQ.DIST.RT(42,4)": "1.66816329414062E-08",
// CHISQ.INV
"CHISQ.INV(0,2)": "0",
"CHISQ.INV(0.75,1)": "1.32330369693147",
"CHISQ.INV(0.1,2)": "0.210721031315653",
"CHISQ.INV(0.8,2)": "3.2188758248682",
"CHISQ.INV(0.25,3)": "1.21253290304567",
// CHISQ.INV.RT
"CHISQ.INV.RT(0.75,1)": "0.101531044267622",
"CHISQ.INV.RT(0.1,2)": "4.60517018598809",
"CHISQ.INV.RT(0.8,2)": "0.446287102628419",
// CONFIDENCE
"CONFIDENCE(0.05,0.07,100)": "0.0137197479028414",
// CONFIDENCE.NORM
"CONFIDENCE.NORM(0.05,0.07,100)": "0.0137197479028414",
// CONFIDENCE.T
"CONFIDENCE.T(0.05,0.07,100)": "0.0138895186611049",
// CORREL
"CORREL(A1:A5,B1:B5)": "1",
// COUNT
"COUNT()": "0",
"COUNT(E1:F2,\"text\",1,INT(2),\"0\")": "4",
// COUNTA
"COUNTA()": "0",
"COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
"COUNTA(COUNTA(1),MUNIT(1))": "2",
"COUNTA(D1:D2)": "2",
// COUNTBLANK
"COUNTBLANK(MUNIT(1))": "0",
"COUNTBLANK(1)": "0",
"COUNTBLANK(B1:C1)": "1",
"COUNTBLANK(C1)": "0",
// COUNTIF
"COUNTIF(D1:D9,\"Jan\")": "4",
"COUNTIF(D1:D9,\"<>Jan\")": "5",
"COUNTIF(A1:F9,\">=50000\")": "2",
"COUNTIF(A1:F9,TRUE)": "0",
// COUNTIFS
"COUNTIFS(A1:A9,2,D1:D9,\"Jan\")": "1",
"COUNTIFS(F1:F9,\">20000\",D1:D9,\"Jan\")": "4",
"COUNTIFS(F1:F9,\">60000\",D1:D9,\"Jan\")": "0",
// CRITBINOM
"CRITBINOM(0,0.5,0.75)": "0",
"CRITBINOM(0.1,0.1,0.75)": "0",
"CRITBINOM(0.6,0.4,0.75)": "0",
"CRITBINOM(2,0.4,0.75)": "1",
"CRITBINOM(100,0.5,20%)": "46",
"CRITBINOM(100,0.5,50%)": "50",
"CRITBINOM(100,0.5,90%)": "56",
// DEVSQ
"DEVSQ(1,3,5,2,9,7)": "47.5",
"DEVSQ(A1:D2)": "10",
// FISHER
"FISHER(-0.9)": "-1.47221948958322",
"FISHER(-0.25)": "-0.255412811882995",
"FISHER(0.8)": "1.09861228866811",
"FISHER(\"0.8\")": "1.09861228866811",
"FISHER(INT(0))": "0",
// FISHERINV
"FISHERINV(-0.2)": "-0.197375320224904",
"FISHERINV(INT(0))": "0",
"FISHERINV(\"0\")": "0",
"FISHERINV(2.8)": "0.992631520201128",
// FORECAST
"FORECAST(7,A1:A7,B1:B7)": "4",
// FORECAST.LINEAR
"FORECAST.LINEAR(7,A1:A7,B1:B7)": "4",
// FREQUENCY
"SUM(FREQUENCY(A2,B2))": "1",
"SUM(FREQUENCY(A1:A5,B1:B2))": "4",
// GAMMA
"GAMMA(0.1)": "9.51350769866873",
"GAMMA(INT(1))": "1",
"GAMMA(1.5)": "0.886226925452758",
"GAMMA(5.5)": "52.3427777845535",
"GAMMA(\"5.5\")": "52.3427777845535",
// GAMMA.DIST
"GAMMA.DIST(6,3,2,FALSE)": "0.112020903827694",
"GAMMA.DIST(6,3,2,TRUE)": "0.576809918873156",
// GAMMADIST
"GAMMADIST(6,3,2,FALSE)": "0.112020903827694",
"GAMMADIST(6,3,2,TRUE)": "0.576809918873156",
// GAMMA.INV
"GAMMA.INV(0.5,3,2)": "5.34812062744712",
"GAMMA.INV(0.5,0.5,1)": "0.227468211559786",
// GAMMAINV
"GAMMAINV(0.5,3,2)": "5.34812062744712",
"GAMMAINV(0.5,0.5,1)": "0.227468211559786",
// GAMMALN
"GAMMALN(4.5)": "2.45373657084244",
"GAMMALN(INT(1))": "0",
// GAMMALN.PRECISE
"GAMMALN.PRECISE(0.4)": "0.796677817701784",
"GAMMALN.PRECISE(4.5)": "2.45373657084244",
// GAUSS
"GAUSS(-5)": "-0.499999713348428",
"GAUSS(0)": "0",
"GAUSS(\"0\")": "0",
"GAUSS(0.1)": "0.039827837277029",
"GAUSS(2.5)": "0.493790334674224",
// GEOMEAN
"GEOMEAN(2.5,3,0.5,1,3)": "1.6226711115996",
// HARMEAN
"HARMEAN(2.5,3,0.5,1,3)": "1.22950819672131",
"HARMEAN(\"2.5\",3,0.5,1,INT(3),\"\")": "1.22950819672131",
// HYPGEOM.DIST
"HYPGEOM.DIST(0,3,3,9,TRUE)": "0.238095238095238",
"HYPGEOM.DIST(1,3,3,9,TRUE)": "0.773809523809524",
"HYPGEOM.DIST(2,3,3,9,TRUE)": "0.988095238095238",
"HYPGEOM.DIST(3,3,3,9,TRUE)": "1",
"HYPGEOM.DIST(1,4,4,12,FALSE)": "0.452525252525253",
"HYPGEOM.DIST(2,4,4,12,FALSE)": "0.339393939393939",
"HYPGEOM.DIST(3,4,4,12,FALSE)": "0.0646464646464646",
"HYPGEOM.DIST(4,4,4,12,FALSE)": "0.00202020202020202",
// HYPGEOMDIST
"HYPGEOMDIST(1,4,4,12)": "0.452525252525253",
"HYPGEOMDIST(2,4,4,12)": "0.339393939393939",
"HYPGEOMDIST(3,4,4,12)": "0.0646464646464646",
"HYPGEOMDIST(4,4,4,12)": "0.00202020202020202",
// INTERCEPT
"INTERCEPT(A1:A4,B1:B4)": "-3",
// KURT
"KURT(F1:F9)": "-1.03350350255137",
"KURT(F1,F2:F9)": "-1.03350350255137",
"KURT(INT(1),MUNIT(2))": "-3.33333333333334",
// EXPON.DIST
"EXPON.DIST(0.5,1,TRUE)": "0.393469340287367",
"EXPON.DIST(0.5,1,FALSE)": "0.606530659712633",
"EXPON.DIST(2,1,TRUE)": "0.864664716763387",
// EXPONDIST
"EXPONDIST(0.5,1,TRUE)": "0.393469340287367",
"EXPONDIST(0.5,1,FALSE)": "0.606530659712633",
"EXPONDIST(2,1,TRUE)": "0.864664716763387",
// FDIST
"FDIST(5,1,2)": "0.154845745271483",
// F.DIST
"F.DIST(1,2,5,TRUE)": "0.568798849628308",
"F.DIST(1,2,5,FALSE)": "0.308000821694066",
// F.DIST.RT
"F.DIST.RT(5,1,2)": "0.154845745271483",
// F.INV
"F.INV(0.9,2,5)": "3.77971607877395",
// FINV
"FINV(0.2,1,2)": "3.55555555555555",
"FINV(0.6,1,2)": "0.380952380952381",
"FINV(0.6,2,2)": "0.666666666666667",
"FINV(0.6,4,4)": "0.763454070045235",
"FINV(0.5,4,8)": "0.914645355977072",
"FINV(0.1,79,86)": "1.32646097270444",
"FINV(1,40,5)": "0",
// F.INV.RT
"F.INV.RT(0.2,1,2)": "3.55555555555555",
"F.INV.RT(0.6,1,2)": "0.380952380952381",
"F.INV.RT(0.6,2,2)": "0.666666666666667",
"F.INV.RT(0.6,4,4)": "0.763454070045235",
"F.INV.RT(0.5,4,8)": "0.914645355977072",
"F.INV.RT(0.1,79,86)": "1.32646097270444",
"F.INV.RT(1,40,5)": "0",
// LOGINV
"LOGINV(0.3,2,0.2)": "6.6533460753367",
// LOGINV
"LOGNORM.INV(0.3,2,0.2)": "6.6533460753367",
// LOGNORM.DIST
"LOGNORM.DIST(0.5,10,5,FALSE)": "0.0162104821842127",
"LOGNORM.DIST(12,10,5,TRUE)": "0.0664171147992078",
// LOGNORMDIST
"LOGNORMDIST(12,10,5)": "0.0664171147992078",
// NEGBINOM.DIST
"NEGBINOM.DIST(6,12,0.5,FALSE)": "0.047210693359375",
"NEGBINOM.DIST(12,12,0.5,FALSE)": "0.0805901288986206",
"NEGBINOM.DIST(15,12,0.5,FALSE)": "0.057564377784729",
"NEGBINOM.DIST(12,12,0.5,TRUE)": "0.58059012889862",
"NEGBINOM.DIST(15,12,0.5,TRUE)": "0.778965830802917",
// NEGBINOMDIST
"NEGBINOMDIST(6,12,0.5)": "0.047210693359375",
"NEGBINOMDIST(12,12,0.5)": "0.0805901288986206",
"NEGBINOMDIST(15,12,0.5)": "0.057564377784729",
// NORM.DIST
"NORM.DIST(0.8,1,0.3,TRUE)": "0.252492537546923",
"NORM.DIST(50,40,20,FALSE)": "0.017603266338215",
// NORMDIST
"NORMDIST(0.8,1,0.3,TRUE)": "0.252492537546923",
"NORMDIST(50,40,20,FALSE)": "0.017603266338215",
// NORM.INV
"NORM.INV(0.6,5,2)": "5.50669420572",
// NORMINV
"NORMINV(0.6,5,2)": "5.50669420572",
"NORMINV(0.99,40,1.5)": "43.489521811582",
"NORMINV(0.02,40,1.5)": "36.9193766364954",
// NORM.S.DIST
"NORM.S.DIST(0.8,TRUE)": "0.788144601416603",
// NORMSDIST
"NORMSDIST(1.333333)": "0.908788725604095",
"NORMSDIST(0)": "0.5",
// NORM.S.INV
"NORM.S.INV(0.25)": "-0.674489750223423",
// NORMSINV
"NORMSINV(0.25)": "-0.674489750223423",
// LARGE
"LARGE(A1:A5,1)": "3",
"LARGE(A1:B5,2)": "4",
"LARGE(A1,1)": "1",
"LARGE(A1:F2,1)": "36693",
// MAX
"MAX(1)": "1",
"MAX(TRUE())": "1",
"MAX(0.5,TRUE())": "1",
"MAX(FALSE())": "0",
"MAX(MUNIT(2))": "1",
"MAX(INT(1))": "1",
"MAX(\"0\",\"2\")": "2",
// MAXA
"MAXA(1)": "1",
"MAXA(TRUE())": "1",
"MAXA(0.5,TRUE())": "1",
"MAXA(FALSE())": "0",
"MAXA(MUNIT(2))": "1",
"MAXA(INT(1))": "1",
"MAXA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "36693",
// MAXIFS
"MAXIFS(F2:F4,A2:A4,\">0\")": "36693",
// MEDIAN
"MEDIAN(A1:A5,12)": "2",
"MEDIAN(A1:A5)": "1.5",
"MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2",
"MEDIAN(\"0\",\"2\")": "1",
// MIN
"MIN(1)": "1",
"MIN(TRUE())": "1",
"MIN(0.5,FALSE())": "0",
"MIN(FALSE())": "0",
"MIN(MUNIT(2))": "0",
"MIN(INT(1))": "1",
"MIN(2,\"1\")": "1",
// MINA
"MINA(1)": "1",
"MINA(TRUE())": "1",
"MINA(0.5,FALSE())": "0",
"MINA(FALSE())": "0",
"MINA(MUNIT(2))": "0",
"MINA(INT(1))": "1",
"MINA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "0",
// MINIFS
"MINIFS(F2:F4,A2:A4,\">0\")": "22100",
// PEARSON
"PEARSON(A1:A4,B1:B4)": "1",
// PERCENTILE.EXC
"PERCENTILE.EXC(A1:A4,0.2)": "0",
"PERCENTILE.EXC(A1:A4,0.6)": "2",
// PERCENTILE.INC
"PERCENTILE.INC(A1:A4,0.2)": "0.6",
// PERCENTILE
"PERCENTILE(A1:A4,0.2)": "0.6",
"PERCENTILE(0,0)": "0",
// PERCENTRANK.EXC
"PERCENTRANK.EXC(A1:B4,0)": "0.142",
"PERCENTRANK.EXC(A1:B4,2)": "0.428",
"PERCENTRANK.EXC(A1:B4,2.5)": "0.5",
"PERCENTRANK.EXC(A1:B4,2.6,1)": "0.5",
"PERCENTRANK.EXC(A1:B4,5)": "0.857",
// PERCENTRANK.INC
"PERCENTRANK.INC(A1:B4,0)": "0",
"PERCENTRANK.INC(A1:B4,2)": "0.4",
"PERCENTRANK.INC(A1:B4,2.5)": "0.5",
"PERCENTRANK.INC(A1:B4,2.6,1)": "0.5",
"PERCENTRANK.INC(A1:B4,5)": "1",
// PERCENTRANK
"PERCENTRANK(A1:B4,0)": "0",
"PERCENTRANK(A1:B4,2)": "0.4",
"PERCENTRANK(A1:B4,2.5)": "0.5",
"PERCENTRANK(A1:B4,2.6,1)": "0.5",
"PERCENTRANK(A1:B4,5)": "1",
// PERMUT
"PERMUT(6,6)": "720",
"PERMUT(7,6)": "5040",
"PERMUT(10,6)": "151200",
// PERMUTATIONA
"PERMUTATIONA(6,6)": "46656",
"PERMUTATIONA(7,6)": "117649",
// PHI
"PHI(-1.5)": "0.129517595665892",
"PHI(0)": "0.398942280401433",
"PHI(0.1)": "0.396952547477012",
"PHI(1)": "0.241970724519143",
// QUARTILE
"QUARTILE(A1:A4,2)": "1.5",
// QUARTILE.EXC
"QUARTILE.EXC(A1:A4,1)": "0.25",
"QUARTILE.EXC(A1:A4,2)": "1.5",
"QUARTILE.EXC(A1:A4,3)": "2.75",
// QUARTILE.INC
"QUARTILE.INC(A1:A4,0)": "0",
// RANK
"RANK(1,A1:B5)": "5",
"RANK(1,A1:B5,0)": "5",
"RANK(1,A1:B5,1)": "2",
// RANK.EQ
"RANK.EQ(1,A1:B5)": "5",
"RANK.EQ(1,A1:B5,0)": "5",
"RANK.EQ(1,A1:B5,1)": "2",
// RSQ
"RSQ(A1:A4,B1:B4)": "1",
// SKEW
"SKEW(1,2,3,4,3)": "-0.404796008910937",
"SKEW(A1:B2)": "0",
"SKEW(A1:D3)": "0",
// SKEW.P
"SKEW.P(1,2,3,4,3)": "-0.27154541788364",
"SKEW.P(A1:B2)": "0",
"SKEW.P(A1:D3)": "0",
// SLOPE
"SLOPE(A1:A4,B1:B4)": "1",
// SMALL
"SMALL(A1:A5,1)": "0",
"SMALL(A1:B5,2)": "1",
"SMALL(A1,1)": "1",
"SMALL(A1:F2,1)": "1",
// STANDARDIZE
"STANDARDIZE(5.5,5,2)": "0.25",
"STANDARDIZE(12,15,1.5)": "-2",
"STANDARDIZE(-2,0,5)": "-0.4",
// STDEVP
"STDEVP(A1:B2,6,-1)": "2.40947204913349",
// STDEV.P
"STDEV.P(A1:B2,6,-1)": "2.40947204913349",
// STDEVPA
"STDEVPA(1,3,5,2)": "1.4790199457749",
"STDEVPA(1,3,5,2,1,0)": "1.63299316185545",
"STDEVPA(1,3,5,2,TRUE,\"text\")": "1.63299316185545",
// T.DIST
"T.DIST(1,10,TRUE)": "0.82955343384897",
"T.DIST(-1,10,TRUE)": "0.17044656615103",
"T.DIST(-1,10,FALSE)": "0.230361989229139",
// T.DIST.2T
"T.DIST.2T(1,10)": "0.34089313230206",
// T.DIST.RT
"T.DIST.RT(1,10)": "0.17044656615103",
"T.DIST.RT(-1,10)": "0.82955343384897",
// TDIST
"TDIST(1,10,1)": "0.17044656615103",
"TDIST(1,10,2)": "0.34089313230206",
// T.INV
"T.INV(0.25,10)": "-0.699812061312432",
"T.INV(0.75,10)": "0.699812061312432",
// T.INV.2T
"T.INV.2T(1,10)": "0",
"T.INV.2T(0.5,10)": "0.699812061312432",
// TINV
"TINV(1,10)": "0",
"TINV(0.5,10)": "0.699812061312432",
// TRIMMEAN
"TRIMMEAN(A1:B4,10%)": "2.5",
"TRIMMEAN(A1:B4,70%)": "2.5",
// VAR
"VAR(1,3,5,0,C1)": "4.91666666666667",
"VAR(1,3,5,0,C1,TRUE)": "4",
// VARA
"VARA(1,3,5,0,C1)": "4.91666666666667",
"VARA(1,3,5,0,C1,TRUE)": "4",
// VARP
"VARP(A1:A5)": "1.25",
"VARP(1,3,5,0,C1,TRUE)": "3.2",
// VAR.P
"VAR.P(A1:A5)": "1.25",
// VAR.S
"VAR.S(1,3,5,0,C1)": "4.91666666666667",
"VAR.S(1,3,5,0,C1,TRUE)": "4",
// VARPA
"VARPA(1,3,5,0,C1)": "3.6875",
"VARPA(1,3,5,0,C1,TRUE)": "3.2",
// WEIBULL
"WEIBULL(1,3,1,FALSE)": "1.10363832351433",
"WEIBULL(2,5,1.5,TRUE)": "0.985212776817482",
// WEIBULL.DIST
"WEIBULL.DIST(1,3,1,FALSE)": "1.10363832351433",
"WEIBULL.DIST(2,5,1.5,TRUE)": "0.985212776817482",
// Information Functions
// ERROR.TYPE
"ERROR.TYPE(1/0)": "2",
"ERROR.TYPE(COT(0))": "2",
"ERROR.TYPE(XOR(\"text\"))": "3",
"ERROR.TYPE(HEX2BIN(2,1))": "6",
"ERROR.TYPE(NA())": "7",
// ISBLANK
"ISBLANK(A1)": "FALSE",
"ISBLANK(A5)": "TRUE",
// ISERR
"ISERR(A1)": "FALSE",
"ISERR(NA())": "FALSE",
"ISERR(POWER(0,-1)))": "TRUE",
// ISERROR
"ISERROR(A1)": "FALSE",
"ISERROR(NA())": "TRUE",
"ISERROR(\"#VALUE!\")": "FALSE",
// ISEVEN
"ISEVEN(A1)": "FALSE",
"ISEVEN(A2)": "TRUE",
"ISEVEN(G1)": "TRUE",
// ISFORMULA
"ISFORMULA(A1)": "FALSE",
"ISFORMULA(\"A\")": "FALSE",
// ISLOGICAL
"ISLOGICAL(TRUE)": "TRUE",
"ISLOGICAL(FALSE)": "TRUE",
"ISLOGICAL(A1=A2)": "TRUE",
"ISLOGICAL(\"true\")": "TRUE",
"ISLOGICAL(\"false\")": "TRUE",
"ISLOGICAL(A1)": "FALSE",
"ISLOGICAL(20/5)": "FALSE",
// ISNA
"ISNA(A1)": "FALSE",
"ISNA(NA())": "TRUE",
// ISNONTEXT
"ISNONTEXT(A1)": "TRUE",
"ISNONTEXT(A5)": "TRUE",
"ISNONTEXT(\"Excelize\")": "FALSE",
"ISNONTEXT(NA())": "TRUE",
// ISNUMBER
"ISNUMBER(A1)": "TRUE",
"ISNUMBER(D1)": "FALSE",
"ISNUMBER(A1:B1)": "TRUE",
// ISODD
"ISODD(A1)": "TRUE",
"ISODD(A2)": "FALSE",
// ISREF
"ISREF(B1)": "TRUE",
"ISREF(B1:B2)": "TRUE",
"ISREF(\"text\")": "FALSE",
"ISREF(B1*B2)": "FALSE",
// ISTEXT
"ISTEXT(D1)": "TRUE",
"ISTEXT(A1)": "FALSE",
// N
"N(10)": "10",
"N(\"10\")": "10",
"N(\"x\")": "0",
"N(TRUE)": "1",
"N(FALSE)": "0",
// SHEET
"SHEET()": "1",
"SHEET(\"Sheet1\")": "1",
// SHEETS
"SHEETS()": "1",
"SHEETS(A1)": "1",
// TYPE
"TYPE(2)": "1",
"TYPE(10/2)": "1",
"TYPE(C2)": "1",
"TYPE(\"text\")": "2",
"TYPE(TRUE)": "4",
"TYPE(NA())": "16",
"TYPE(MUNIT(2))": "64",
// T
"T(\"text\")": "text",
"T(N(10))": "",
// Logical Functions
// AND
"AND(0)": "FALSE",
"AND(1)": "TRUE",
"AND(1,0)": "FALSE",
"AND(0,1)": "FALSE",
"AND(1=1)": "TRUE",
"AND(1<2)": "TRUE",
"AND(1>2,2<3,2>0,3>1)": "FALSE",
"AND(1=1),1=1": "TRUE",
"AND(\"TRUE\",\"FALSE\")": "FALSE",
// FALSE
"FALSE()": "FALSE",
// IFERROR
"IFERROR(1/2,0)": "0.5",
"IFERROR(ISERROR(),0)": "0",
"IFERROR(1/0,0)": "0",
"IFERROR(G1,2)": "0",
"IFERROR(B2/MROUND(A2,1),0)": "2.5",
// IFNA
"IFNA(1,\"not found\")": "1",
"IFNA(NA(),\"not found\")": "not found",
"IFNA(HLOOKUP(D2,D:D,1,2),\"not found\")": "not found",
// IFS
"IFS(4>1,5/4,4<-1,-5/4,TRUE,0)": "1.25",
"IFS(-2>1,5/-2,-2<-1,-5/-2,TRUE,0)": "2.5",
"IFS(0>1,5/0,0<-1,-5/0,TRUE,0)": "0",
// NOT
"NOT(FALSE())": "TRUE",
"NOT(\"false\")": "TRUE",
"NOT(\"true\")": "FALSE",
"NOT(ISBLANK(B1))": "TRUE",
// OR
"OR(1)": "TRUE",
"OR(0)": "FALSE",
"OR(1=2,2=2)": "TRUE",
"OR(1=2,2=3)": "FALSE",
"OR(1=1,2=3)": "TRUE",
"OR(\"TRUE\",\"FALSE\")": "TRUE",
"OR(A1:B1)": "TRUE",
// SWITCH
"SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A",
"SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C",
"SWITCH(4,1,\"A\",2,\"B\",3,\"C\",\"N\")": "N",
// TRUE
"TRUE()": "TRUE",
// XOR
"XOR(1>0,2>0)": "FALSE",
"XOR(1>0,0>1)": "TRUE",
"XOR(1>0,0>1,INT(0),INT(1),A1:A4,2)": "FALSE",
// Date and Time Functions
// DATE
"DATE(2020,10,21)": "44125",
"DATE(2020,10,21)+1": "44126",
"DATE(1900,1,1)": "1",
// DATEDIF
"DATEDIF(43101,43101,\"D\")": "0",
"DATEDIF(43101,43891,\"d\")": "790",
"DATEDIF(43101,43891,\"Y\")": "2",
"DATEDIF(42156,44242,\"y\")": "5",
"DATEDIF(43101,43891,\"M\")": "26",
"DATEDIF(42171,44242,\"m\")": "67",
"DATEDIF(42156,44454,\"MD\")": "14",
"DATEDIF(42171,44242,\"md\")": "30",
"DATEDIF(43101,43891,\"YM\")": "2",
"DATEDIF(42171,44242,\"ym\")": "7",
"DATEDIF(43101,43891,\"YD\")": "59",
"DATEDIF(36526,73110,\"YD\")": "60",
"DATEDIF(42171,44242,\"yd\")": "244",
// DATEVALUE
"DATEVALUE(\"01/01/16\")": "42370",
"DATEVALUE(\"01/01/2016\")": "42370",
"DATEVALUE(\"01/01/29\")": "47119",
"DATEVALUE(\"01/01/30\")": "10959",
// DAY
"DAY(0)": "0",
"DAY(INT(7))": "7",
"DAY(\"35\")": "4",
"DAY(42171)": "16",
"DAY(\"2-28-1900\")": "28",
"DAY(\"31-May-2015\")": "31",
"DAY(\"01/03/2019 12:14:16\")": "3",
"DAY(\"January 25, 2020 01 AM\")": "25",
"DAY(\"January 25, 2020 01:03 AM\")": "25",
"DAY(\"January 25, 2020 12:00:00 AM\")": "25",
"DAY(\"1900-1-1\")": "1",
"DAY(\"12-1-1900\")": "1",
"DAY(\"3-January-1900\")": "3",
"DAY(\"3-February-2000\")": "3",
"DAY(\"3-February-2008\")": "3",
"DAY(\"01/25/20\")": "25",
"DAY(\"01/25/31\")": "25",
// DAYS
"DAYS(2,1)": "1",
"DAYS(INT(2),INT(1))": "1",
"DAYS(\"02/02/2015\",\"01/01/2015\")": "32",
// DAYS360
"DAYS360(\"10/10/2020\", \"10/10/2020\")": "0",
"DAYS360(\"01/30/1999\", \"02/28/1999\")": "28",
"DAYS360(\"01/31/1999\", \"02/28/1999\")": "28",
"DAYS360(\"12/12/1999\", \"08/31/1999\")": "-101",
"DAYS360(\"12/12/1999\", \"11/30/1999\")": "-12",
"DAYS360(\"12/12/1999\", \"11/30/1999\",TRUE)": "-12",
"DAYS360(\"01/31/1999\", \"03/31/1999\",TRUE)": "60",
"DAYS360(\"01/31/1999\", \"03/31/2000\",FALSE)": "420",
// EDATE
"EDATE(\"01/01/2021\",-1)": "44166",
"EDATE(\"01/31/2020\",1)": "43890",
"EDATE(\"01/29/2020\",12)": "44225",
"EDATE(\"6/12/2021\",-14)": "43933",
// EOMONTH
"EOMONTH(\"01/01/2021\",-1)": "44196",
"EOMONTH(\"01/29/2020\",12)": "44227",
"EOMONTH(\"01/12/2021\",-18)": "43677",
// HOUR
"HOUR(1)": "0",
"HOUR(43543.5032060185)": "12",
"HOUR(\"43543.5032060185\")": "12",
"HOUR(\"13:00:55\")": "13",
"HOUR(\"1:00 PM\")": "13",
"HOUR(\"12/09/2015 08:55\")": "8",
// ISOWEEKNUM
"ISOWEEKNUM(42370)": "53",
"ISOWEEKNUM(\"42370\")": "53",
"ISOWEEKNUM(\"01/01/2005\")": "53",
"ISOWEEKNUM(\"02/02/2005\")": "5",
// MINUTE
"MINUTE(1)": "0",
"MINUTE(0.04)": "57",
"MINUTE(\"0.04\")": "57",
"MINUTE(\"13:35:55\")": "35",
"MINUTE(\"12/09/2015 08:55\")": "55",
// MONTH
"MONTH(42171)": "6",
"MONTH(\"31-May-2015\")": "5",
// YEAR
"YEAR(15)": "1900",
"YEAR(\"15\")": "1900",
"YEAR(2048)": "1905",
"YEAR(42171)": "2015",
"YEAR(\"29-May-2015\")": "2015",
"YEAR(\"05/03/1984\")": "1984",
// YEARFRAC
"YEARFRAC(42005,42005)": "0",
"YEARFRAC(42005,42094)": "0.25",
"YEARFRAC(42005,42094,0)": "0.25",
"YEARFRAC(42005,42094,1)": "0.243835616438356",
"YEARFRAC(42005,42094,2)": "0.247222222222222",
"YEARFRAC(42005,42094,3)": "0.243835616438356",
"YEARFRAC(42005,42094,4)": "0.247222222222222",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\")": "0.25",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\",0)": "0.25",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\",1)": "0.243835616438356",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\",2)": "0.247222222222222",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\",3)": "0.243835616438356",
"YEARFRAC(\"01/01/2015\",\"03/31/2015\",4)": "0.247222222222222",
"YEARFRAC(\"01/01/2015\",42094)": "0.25",
"YEARFRAC(42005,\"03/31/2015\",0)": "0.25",
"YEARFRAC(\"01/31/2015\",\"03/31/2015\")": "0.166666666666667",
"YEARFRAC(\"01/30/2015\",\"03/31/2015\")": "0.166666666666667",
"YEARFRAC(\"02/29/2000\", \"02/29/2008\")": "8",
"YEARFRAC(\"02/29/2000\", \"02/29/2008\",1)": "7.99817518248175",
"YEARFRAC(\"02/29/2000\", \"01/29/2001\",1)": "0.915300546448087",
"YEARFRAC(\"02/29/2000\", \"03/29/2000\",1)": "0.0792349726775956",
"YEARFRAC(\"01/31/2000\", \"03/29/2000\",4)": "0.163888888888889",
// SECOND
"SECOND(\"13:35:55\")": "55",
"SECOND(\"13:10:60\")": "0",
"SECOND(\"13:10:61\")": "1",
"SECOND(\"08:17:00\")": "0",
"SECOND(\"12/09/2015 08:55\")": "0",
"SECOND(\"12/09/2011 08:17:23\")": "23",
"SECOND(\"43543.5032060185\")": "37",
"SECOND(43543.5032060185)": "37",
// TIME
"TIME(5,44,32)": "0.239259259259259",
"TIME(\"5\",\"44\",\"32\")": "0.239259259259259",
"TIME(0,0,73)": "0.000844907407407407",
// TIMEVALUE
"TIMEVALUE(\"2:23\")": "0.0993055555555555",
"TIMEVALUE(\"2:23 am\")": "0.0993055555555555",
"TIMEVALUE(\"2:23 PM\")": "0.599305555555556",
"TIMEVALUE(\"14:23:00\")": "0.599305555555556",
"TIMEVALUE(\"00:02:23\")": "0.00165509259259259",
"TIMEVALUE(\"01/01/2011 02:23\")": "0.0993055555555555",
// WEEKDAY
"WEEKDAY(0)": "7",
"WEEKDAY(47119)": "2",
"WEEKDAY(\"12/25/2012\")": "3",
"WEEKDAY(\"12/25/2012\",1)": "3",
"WEEKDAY(\"12/25/2012\",2)": "2",
"WEEKDAY(\"12/25/2012\",3)": "1",
"WEEKDAY(\"12/25/2012\",11)": "2",
"WEEKDAY(\"12/25/2012\",12)": "1",
"WEEKDAY(\"12/25/2012\",13)": "7",
"WEEKDAY(\"12/25/2012\",14)": "6",
"WEEKDAY(\"12/25/2012\",15)": "5",
"WEEKDAY(\"12/25/2012\",16)": "4",
"WEEKDAY(\"12/25/2012\",17)": "3",
// WEEKNUM
"WEEKNUM(\"01/01/2011\")": "1",
"WEEKNUM(\"01/03/2011\")": "2",
"WEEKNUM(\"01/13/2008\")": "3",
"WEEKNUM(\"01/21/2008\")": "4",
"WEEKNUM(\"01/30/2008\")": "5",
"WEEKNUM(\"02/04/2008\")": "6",
"WEEKNUM(\"01/02/2017\",2)": "2",
"WEEKNUM(\"01/02/2017\",12)": "1",
"WEEKNUM(\"12/31/2017\",21)": "52",
"WEEKNUM(\"01/01/2017\",21)": "52",
"WEEKNUM(\"01/01/2021\",21)": "53",
// Text Functions
// ARRAYTOTEXT
"ARRAYTOTEXT(A1:D2)": "1, 4, , Month, 2, 5, , Jan",
"ARRAYTOTEXT(A1:D2,0)": "1, 4, , Month, 2, 5, , Jan",
"ARRAYTOTEXT(A1:D2,1)": "{1,4,,\"Month\";2,5,,\"Jan\"}",
// BAHTTEXT
"BAHTTEXT(-1.1)": "\u0e25\u0e1a\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e2a\u0e34\u0e1a\u0e2a\u0e15\u0e32\u0e07\u0e04\u0e4c",
"BAHTTEXT(0)": "\u0e28\u0e39\u0e19\u0e22\u0e4c\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(1)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(1.1)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1a\u0e32\u0e17\u0e2a\u0e34\u0e1a\u0e2a\u0e15\u0e32\u0e07\u0e04\u0e4c",
"BAHTTEXT(2)": "\u0e2a\u0e2d\u0e07\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(3)": "\u0e2a\u0e32\u0e21\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(4)": "\u0e2a\u0e35\u0e48\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(5)": "\u0e2b\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(6)": "\u0e2b\u0e01\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(7)": "\u0e40\u0e08\u0e47\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(8)": "\u0e41\u0e1b\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(9)": "\u0e40\u0e01\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(10)": "\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(11)": "\u0e2a\u0e34\u0e1a\u0e40\u0e2d\u0e47\u0e14\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(15)": "\u0e2a\u0e34\u0e1a\u0e2b\u0e49\u0e32\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(20)": "\u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(100)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e23\u0e49\u0e2d\u0e22\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(130)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e23\u0e49\u0e2d\u0e22\u0e2a\u0e32\u0e21\u0e2a\u0e34\u0e1a\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(1000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e1e\u0e31\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(10000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e2b\u0e21\u0e37\u0e48\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(100000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e41\u0e2a\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
"BAHTTEXT(1000000)": "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e25\u0e49\u0e32\u0e19\u0e1a\u0e32\u0e17\u0e16\u0e49\u0e27\u0e19",
// CHAR
"CHAR(65)": "A",
"CHAR(97)": "a",
"CHAR(63)": "?",
"CHAR(51)": "3",
// CLEAN
"CLEAN(\"\u0009clean text\")": "clean text",
"CLEAN(0)": "0",
// CODE
"CODE(\"Alpha\")": "65",
"CODE(\"alpha\")": "97",
"CODE(\"?\")": "63",
"CODE(\"3\")": "51",
"CODE(\"\")": "0",
// CONCAT
"CONCAT(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02",
"CONCAT(MUNIT(2))": "1001",
"CONCAT(A1:B2)": "1425",
// CONCATENATE
"CONCATENATE(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02",
"CONCATENATE(MUNIT(2))": "1001",
"CONCATENATE(A1:B2)": "1425",
// DBCS
"DBCS(\"\")": "",
"DBCS(123.456)": "123.456",
"DBCS(\"123.456\")": "123.456",
// EXACT
"EXACT(1,\"1\")": "TRUE",
"EXACT(1,1)": "TRUE",
"EXACT(\"A\",\"a\")": "FALSE",
// FIXED
"FIXED(5123.591)": "5,123.591",
"FIXED(5123.591,1)": "5,123.6",
"FIXED(5123.591,0)": "5,124",
"FIXED(5123.591,-1)": "5,120",
"FIXED(5123.591,-2)": "5,100",
"FIXED(5123.591,-3,TRUE)": "5000",
"FIXED(5123.591,-5)": "0",
"FIXED(-77262.23973,-5)": "-100,000",
// FIND
"FIND(\"T\",\"Original Text\")": "10",
"FIND(\"t\",\"Original Text\")": "13",
"FIND(\"i\",\"Original Text\")": "3",
"FIND(\"i\",\"Original Text\",4)": "5",
"FIND(\"\",\"Original Text\")": "1",
"FIND(\"\",\"Original Text\",2)": "2",
"FIND(\"s\",\"Sales\",2)": "5",
"FIND(D1:E2,\"Month\")": "1",
// FINDB
"FINDB(\"T\",\"Original Text\")": "10",
"FINDB(\"t\",\"Original Text\")": "13",
"FINDB(\"i\",\"Original Text\")": "3",
"FINDB(\"i\",\"Original Text\",4)": "5",
"FINDB(\"\",\"Original Text\")": "1",
"FINDB(\"\",\"Original Text\",2)": "2",
"FINDB(\"s\",\"Sales\",2)": "5",
// LEFT
"LEFT(\"Original Text\")": "O",
"LEFT(\"Original Text\",4)": "Orig",
"LEFT(\"Original Text\",0)": "",
"LEFT(\"Original Text\",13)": "Original Text",
"LEFT(\"Original Text\",20)": "Original Text",
"LEFT(\"オリジナルテキスト\")": "オ",
"LEFT(\"オリジナルテキスト\",2)": "オリ",
"LEFT(\"オリジナルテキスト\",5)": "オリジナル",
"LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ",
"LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
// LEFTB
"LEFTB(\"Original Text\")": "O",
"LEFTB(\"Original Text\",4)": "Orig",
"LEFTB(\"Original Text\",0)": "",
"LEFTB(\"Original Text\",13)": "Original Text",
"LEFTB(\"Original Text\",20)": "Original Text",
// LEN
"LEN(\"\")": "0",
"LEN(D1)": "5",
"LEN(\"テキスト\")": "4",
"LEN(\"オリジナルテキスト\")": "9",
"LEN(7+LEN(A1&B1&C1))": "1",
"LEN(8+LEN(A1+(C1-B1)))": "2",
// LENB
"LENB(\"\")": "0",
"LENB(D1)": "5",
"LENB(\"テキスト\")": "8",
"LENB(\"オリジナルテキスト\")": "18",
// LOWER
"LOWER(\"test\")": "test",
"LOWER(\"TEST\")": "test",
"LOWER(\"Test\")": "test",
"LOWER(\"TEST 123\")": "test 123",
// MID
"MID(\"Original Text\",7,1)": "a",
"MID(\"Original Text\",4,7)": "ginal T",
"MID(\"255 years\",3,1)": "5",
"MID(\"text\",3,6)": "xt",
"MID(\"text\",6,0)": "",
"MID(\"你好World\",5,1)": "r",
"MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8",
"MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD",
// MIDB
"MIDB(\"Original Text\",7,1)": "a",
"MIDB(\"Original Text\",4,7)": "ginal T",
"MIDB(\"255 years\",3,1)": "5",
"MIDB(\"text\",3,6)": "xt",
"MIDB(\"text\",6,0)": "",
"MIDB(\"你好World\",5,1)": "W",
"MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA",
"MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3",
// PROPER
"PROPER(\"this is a test sentence\")": "This Is A Test Sentence",
"PROPER(\"THIS IS A TEST SENTENCE\")": "This Is A Test Sentence",
"PROPER(\"123tEST teXT\")": "123Test Text",
"PROPER(\"Mr. SMITH's address\")": "Mr. Smith'S Address",
// REPLACE
"REPLACE(\"test string\",7,3,\"X\")": "test sXng",
"REPLACE(\"second test string\",8,4,\"XXX\")": "second XXX string",
"REPLACE(\"text\",5,0,\" and char\")": "text and char",
"REPLACE(\"text\",1,20,\"char and \")": "char and ",
// REPLACEB
"REPLACEB(\"test string\",7,3,\"X\")": "test sXng",
"REPLACEB(\"second test string\",8,4,\"XXX\")": "second XXX string",
"REPLACEB(\"text\",5,0,\" and char\")": "text and char",
"REPLACEB(\"text\",1,20,\"char and \")": "char and ",
// REPT
"REPT(\"*\",0)": "",
"REPT(\"*\",1)": "*",
"REPT(\"**\",2)": "****",
// RIGHT
"RIGHT(\"Original Text\")": "t",
"RIGHT(\"Original Text\",4)": "Text",
"RIGHT(\"Original Text\",0)": "",
"RIGHT(\"Original Text\",13)": "Original Text",
"RIGHT(\"Original Text\",20)": "Original Text",
"RIGHT(\"オリジナルテキスト\")": "ト",
"RIGHT(\"オリジナルテキスト\",2)": "スト",
"RIGHT(\"オリジナルテキスト\",4)": "テキスト",
"RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト",
"RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
// RIGHTB
"RIGHTB(\"Original Text\")": "t",
"RIGHTB(\"Original Text\",4)": "Text",
"RIGHTB(\"Original Text\",0)": "",
"RIGHTB(\"Original Text\",13)": "Original Text",
"RIGHTB(\"Original Text\",20)": "Original Text",
// SUBSTITUTE
"SUBSTITUTE(\"abab\",\"a\",\"X\")": "XbXb",
"SUBSTITUTE(\"abab\",\"a\",\"X\",2)": "abXb",
"SUBSTITUTE(\"abab\",\"x\",\"X\",2)": "abab",
"SUBSTITUTE(\"John is 5 years old\",\"John\",\"Jack\")": "Jack is 5 years old",
"SUBSTITUTE(\"John is 5 years old\",\"5\",\"6\")": "John is 6 years old",
// TEXT
"TEXT(\"07/07/2015\",\"mm/dd/yyyy\")": "07/07/2015",
"TEXT(42192,\"mm/dd/yyyy\")": "07/07/2015",
"TEXT(42192,\"mmm dd yyyy\")": "Jul 07 2015",
"TEXT(0.75,\"hh:mm\")": "18:00",
"TEXT(36.363636,\"0.00\")": "36.36",
"TEXT(567.9,\"$#,##0.00\")": "$567.90",
"TEXT(-5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "- $5.00",
"TEXT(5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "+ $5.00",
// TEXTAFTER
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\")": "'s, red hood",
"TEXTAFTER(\"Red riding hood's, red hood\",\"HOOD\",1,1)": "'s, red hood",
"TEXTAFTER(\"Red riding hood's, red hood\",\"basket\",1,0,0,\"x\")": "x",
"TEXTAFTER(\"Red riding hood's, red hood\",\"basket\",1,0,1,\"x\")": "",
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",-1)": "",
"TEXTAFTER(\"Jones,Bob\",\",\")": "Bob",
"TEXTAFTER(\"12 ft x 20 ft\",\" x \")": "20 ft",
"TEXTAFTER(\"ABX-112-Red-Y\",\"-\",1)": "112-Red-Y",
"TEXTAFTER(\"ABX-112-Red-Y\",\"-\",2)": "Red-Y",
"TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-1)": "Y",
"TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-2)": "Red-Y",
"TEXTAFTER(\"ABX-112-Red-Y\",\"-\",-3)": "112-Red-Y",
"TEXTAFTER(\"ABX-123-Red-XYZ\",\"-\",-4,0,1)": "ABX-123-Red-XYZ",
"TEXTAFTER(\"ABX-123-Red-XYZ\",\"A\")": "BX-123-Red-XYZ",
// TEXTBEFORE
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\")": "Red riding ",
"TEXTBEFORE(\"Red riding hood's, red hood\",\"HOOD\",1,1)": "Red riding ",
"TEXTBEFORE(\"Red riding hood's, red hood\",\"basket\",1,0,0,\"x\")": "x",
"TEXTBEFORE(\"Red riding hood's, red hood\",\"basket\",1,0,1,\"x\")": "Red riding hood's, red hood",
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",-1)": "Red riding hood's, red ",
"TEXTBEFORE(\"Jones,Bob\",\",\")": "Jones",
"TEXTBEFORE(\"12 ft x 20 ft\",\" x \")": "12 ft",
"TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",1)": "ABX",
"TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",2)": "ABX-112",
"TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",-1)": "ABX-112-Red",
"TEXTBEFORE(\"ABX-112-Red-Y\",\"-\",-2)": "ABX-112",
"TEXTBEFORE(\"ABX-123-Red-XYZ\",\"-\",4,0,1)": "ABX-123-Red-XYZ",
"TEXTBEFORE(\"ABX-112-Red-Y\",\"A\")": "",
// TEXTJOIN
"TEXTJOIN(\"-\",TRUE,1,2,3,4)": "1-2-3-4",
"TEXTJOIN(A4,TRUE,A1:B2)": "1040205",
"TEXTJOIN(\",\",FALSE,A1:C2)": "1,4,,2,5,",
"TEXTJOIN(\",\",TRUE,A1:C2)": "1,4,2,5",
"TEXTJOIN(\",\",TRUE,MUNIT(2))": "1,0,0,1",
// TRIM
"TRIM(\" trim text \")": "trim text",
"TRIM(0)": "0",
// UNICHAR
"UNICHAR(65)": "A",
"UNICHAR(97)": "a",
"UNICHAR(63)": "?",
"UNICHAR(51)": "3",
// UNICODE
"UNICODE(\"Alpha\")": "65",
"UNICODE(\"alpha\")": "97",
"UNICODE(\"?\")": "63",
"UNICODE(\"3\")": "51",
// UNIQUE
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9))": "Jan,Feb",
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, FALSE))": "Jan,Feb",
"TEXTJOIN(\",\", TRUE, UNIQUE(E2:E9, FALSE, FALSE))": "North 1,North 2,South 1,South 2",
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, TRUE))": "",
// UPPER
"UPPER(\"test\")": "TEST",
"UPPER(\"TEST\")": "TEST",
"UPPER(\"Test\")": "TEST",
"UPPER(\"TEST 123\")": "TEST 123",
// VALUE
"VALUE(\"50\")": "50",
"VALUE(\"1.0E-07\")": "0.0000001",
"VALUE(\"5,000\")": "5000",
"VALUE(\"20%\")": "0.2",
"VALUE(\"12:00:00\")": "0.5",
"VALUE(\"01/02/2006 15:04:05\")": "38719.6278356481",
// VALUETOTEXT
"VALUETOTEXT(A1)": "1",
"VALUETOTEXT(A1,0)": "1",
"VALUETOTEXT(A1,1)": "1",
"VALUETOTEXT(D1)": "Month",
"VALUETOTEXT(D1,0)": "Month",
"VALUETOTEXT(D1,1)": "\"Month\"",
// Conditional Functions
// IF
"IF(1=1)": "TRUE",
"IF(1<>1)": "FALSE",
"IF(5<0, \"negative\", \"positive\")": "positive",
"IF(-2<0, \"negative\", \"positive\")": "negative",
"IF(1=1, \"equal\", \"notequal\")": "equal",
"IF(1<>1, \"equal\", \"notequal\")": "notequal",
"IF(\"A\"=\"A\", \"equal\", \"notequal\")": "equal",
"IF(\"A\"<>\"A\", \"equal\", \"notequal\")": "notequal",
"IF(FALSE,0,ROUND(4/2,0))": "2",
"IF(TRUE,ROUND(4/2,0),0)": "2",
"IF(A4>0.4,\"TRUE\",\"FALSE\")": "FALSE",
// Excel Lookup and Reference Functions
// ADDRESS
"ADDRESS(1,1,1,TRUE)": "$A$1",
"ADDRESS(1,2,1,TRUE)": "$B$1",
"ADDRESS(1,1,1,FALSE)": "R1C1",
"ADDRESS(1,2,1,FALSE)": "R1C2",
"ADDRESS(1,1,2,TRUE)": "A$1",
"ADDRESS(1,2,2,TRUE)": "B$1",
"ADDRESS(1,1,2,FALSE)": "R1C[1]",
"ADDRESS(1,2,2,FALSE)": "R1C[2]",
"ADDRESS(1,1,3,TRUE)": "$A1",
"ADDRESS(1,2,3,TRUE)": "$B1",
"ADDRESS(1,1,3,FALSE)": "R[1]C1",
"ADDRESS(1,2,3,FALSE)": "R[1]C2",
"ADDRESS(1,1,4,TRUE)": "A1",
"ADDRESS(1,2,4,TRUE)": "B1",
"ADDRESS(1,1,4,FALSE)": "R[1]C[1]",
"ADDRESS(1,2,4,FALSE)": "R[1]C[2]",
"ADDRESS(1,1,4,TRUE,\"\")": "!A1",
"ADDRESS(1,2,4,TRUE,\"\")": "!B1",
"ADDRESS(1,1,4,TRUE,\"Sheet1\")": "Sheet1!A1",
// CHOOSE
"CHOOSE(4,\"red\",\"blue\",\"green\",\"brown\")": "brown",
"CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red",
"SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))": "9",
// COLUMN
"COLUMN()": "3",
"COLUMN(Sheet1!A1)": "1",
"COLUMN(Sheet1!A1:B1:C1)": "1",
"COLUMN(Sheet1!F1:G1)": "6",
"COLUMN(H1)": "8",
// COLUMNS
"COLUMNS(B1)": "1",
"COLUMNS(1:1)": "16384",
"COLUMNS(Sheet1!1:1)": "16384",
"COLUMNS(B1:E5)": "4",
"COLUMNS(Sheet1!E5:H7:B1)": "7",
"COLUMNS(E5:H7:B1:C1:Z1:C1:B1)": "25",
"COLUMNS(E5:B1)": "4",
"COLUMNS(EM38:HZ81)": "92",
// HLOOKUP
"HLOOKUP(D2,D2:D8,1,FALSE)": "Jan",
"HLOOKUP(F3,F3:F8,3,FALSE)": "34440",
"HLOOKUP(INT(F3),F3:F8,3,FALSE)": "34440",
"HLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
// HYPERLINK
"HYPERLINK(\"https://github.com/xuri/excelize\")": "https://github.com/xuri/excelize",
"HYPERLINK(\"https://github.com/xuri/excelize\",\"Excelize\")": "Excelize",
// VLOOKUP
"VLOOKUP(D2,D:D,1,FALSE)": "Jan",
"VLOOKUP(D2,D1:D10,1)": "Jan",
"VLOOKUP(D2,D1:D11,1)": "Feb",
"VLOOKUP(D2,D1:D10,1,FALSE)": "Jan",
"VLOOKUP(INT(36693),F2:F2,1,FALSE)": "36693",
"VLOOKUP(INT(F2),F3:F9,1)": "32080",
"VLOOKUP(INT(F2),F3:F9,1,TRUE)": "32080",
"VLOOKUP(MUNIT(3),MUNIT(3),1)": "0",
"VLOOKUP(A1,A3:B5,1)": "0",
"VLOOKUP(A1:A2,A1:A1,1)": "1",
"VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
// INDEX
"INDEX(0,0,0)": "0",
"INDEX(A1,0,0)": "1",
"INDEX(A1:A1,0,0)": "1",
"SUM(INDEX(A1:B1,1))": "5",
"SUM(INDEX(A1:B1,1,0))": "5",
"SUM(INDEX(A1:B2,2,0))": "7",
"SUM(INDEX(A1:B4,0,2))": "9",
"SUM(INDEX(E1:F5,5,2))": "34440",
// INDIRECT
"INDIRECT(\"E1\")": "Team",
"INDIRECT(\"E\"&1)": "Team",
"INDIRECT(\"E\"&ROW())": "Team",
"INDIRECT(\"E\"&ROW(),TRUE)": "Team",
"INDIRECT(\"R1C5\",FALSE)": "Team",
"INDIRECT(\"R\"&1&\"C\"&5,FALSE)": "Team",
"SUM(INDIRECT(\"A1:B2\"))": "12",
"SUM(INDIRECT(\"A1:B2\",TRUE))": "12",
"SUM(INDIRECT(\"R1C1:R2C2\",FALSE))": "12",
// LOOKUP
"LOOKUP(F8,F8:F9,F8:F9)": "32080",
"LOOKUP(F8,F8:F9,D8:D9)": "Feb",
"LOOKUP(E3,E2:E5,F2:F5)": "22100",
"LOOKUP(E3,E2:F5)": "22100",
"LOOKUP(F3+1,F3:F4,F3:F4)": "22100",
"LOOKUP(F4+1,F3:F4,F3:F4)": "53321",
"LOOKUP(1,MUNIT(1))": "1",
"LOOKUP(1,MUNIT(1),MUNIT(1))": "1",
// ROW
"ROW()": "1",
"ROW(Sheet1!A1)": "1",
"ROW(Sheet1!A1:B2:C3)": "1",
"ROW(Sheet1!F5:G6)": "5",
"ROW(A8)": "8",
// ROWS
"ROWS(B1)": "1",
"ROWS(B:B)": "1048576",
"ROWS(Sheet1!B:B)": "1048576",
"ROWS(B1:E5)": "5",
"ROWS(Sheet1!E5:H7:B1)": "7",
"ROWS(E5:H8:B2:C3:Z26:C3:B2)": "25",
"ROWS(E5:B1)": "5",
"ROWS(EM38:HZ81)": "44",
// Web Functions
// ENCODEURL
"ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As",
// Financial Functions
// ACCRINT
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,0,TRUE)": "1600",
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,0,FALSE)": "1600",
// ACCRINTM
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000)": "800",
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,3)": "800",
// AMORDEGRC
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%)": "42",
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,4)": "42",
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,40%,4)": "42",
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,25%,4)": "41",
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",109,1,25%,4)": "54",
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",110,2,25%,4)": "0",
// AMORLINC
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,4)": "30",
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,0%,4)": "0",
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,20,15%,4)": "0",
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,6,15%,4)": "0.6875",
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,0,15%,4)": "16.8125",
// COUPDAYBS
"COUPDAYBS(\"02/24/2000\",\"11/24/2000\",4,4)": "0",
"COUPDAYBS(\"03/27/2000\",\"11/29/2000\",4,4)": "28",
"COUPDAYBS(\"02/29/2000\",\"04/01/2000\",4,4)": "58",
"COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4)": "66",
"COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4,1)": "68",
"COUPDAYBS(\"10/31/2011\",\"02/26/2012\",4,0)": "65",
// COUPDAYS
"COUPDAYS(\"01/01/2011\",\"10/25/2012\",4)": "90",
"COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,1)": "92",
// COUPDAYSNC
"COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4)": "24",
"COUPDAYSNC(\"04/01/2012\",\"03/31/2020\",2)": "179",
// COUPNCD
"COUPNCD(\"01/01/2011\",\"10/25/2012\",4)": "40568",
"COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40568",
"COUPNCD(\"10/25/2011\",\"01/01/2012\",4)": "40909",
"COUPNCD(\"04/01/2012\",\"03/31/2020\",2)": "41182",
"COUPNCD(\"01/01/2000\",\"08/30/2001\",2)": "36585",
// COUPNUM
"COUPNUM(\"01/01/2011\",\"10/25/2012\",4)": "8",
"COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0)": "8",
"COUPNUM(\"09/30/2017\",\"03/31/2021\",4,0)": "14",
// COUPPCD
"COUPPCD(\"01/01/2011\",\"10/25/2012\",4)": "40476",
"COUPPCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40476",
"COUPPCD(\"10/25/2011\",\"01/01/2012\",4)": "40817",
// CUMIPMT
"CUMIPMT(0.05/12,60,50000,1,12,0)": "-2294.97753732664",
"CUMIPMT(0.05/12,60,50000,13,24,0)": "-1833.10006657389",
// CUMPRINC
"CUMPRINC(0.05/12,60,50000,1,12,0)": "-9027.76264907988",
"CUMPRINC(0.05/12,60,50000,13,24,0)": "-9489.64011983263",
// DB
"DB(0,1000,5,1)": "0",
"DB(10000,1000,5,1)": "3690",
"DB(10000,1000,5,2)": "2328.39",
"DB(10000,1000,5,1,6)": "1845",
"DB(10000,1000,5,6,6)": "238.527124587882",
// DDB
"DDB(0,1000,5,1)": "0",
"DDB(10000,1000,5,1)": "4000",
"DDB(10000,1000,5,2)": "2400",
"DDB(10000,1000,5,3)": "1440",
"DDB(10000,1000,5,4)": "864",
"DDB(10000,1000,5,5)": "296",
// DISC
"DISC(\"04/01/2016\",\"03/31/2021\",95,100)": "0.01",
// DOLLAR
"DOLLAR(1234.56)": "$1,234.56",
"DOLLAR(1234.56,0)": "$1,235",
"DOLLAR(1234.56,1)": "$1,234.6",
"DOLLAR(1234.56,2)": "$1,234.56",
"DOLLAR(1234.56,3)": "$1,234.560",
"DOLLAR(1234.56,-2)": "$1,200",
"DOLLAR(1234.56,-3)": "$1,000",
"DOLLAR(-1234.56,3)": "($1,234.560)",
"DOLLAR(-1234.56,-3)": "($1,000)",
// DOLLARDE
"DOLLARDE(1.01,16)": "1.0625",
// DOLLARFR
"DOLLARFR(1.0625,16)": "1.01",
// DURATION
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4)": "6.67442279848313",
// EFFECT
"EFFECT(0.1,4)": "0.103812890625",
"EFFECT(0.025,2)": "0.02515625",
// EUROCONVERT
"EUROCONVERT(1.47,\"EUR\",\"EUR\")": "1.47",
"EUROCONVERT(1.47,\"EUR\",\"DEM\")": "2.88",
"EUROCONVERT(1.47,\"FRF\",\"DEM\")": "0.44",
"EUROCONVERT(1.47,\"FRF\",\"DEM\",FALSE)": "0.44",
"EUROCONVERT(1.47,\"FRF\",\"DEM\",FALSE,3)": "0.44",
"EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,3)": "0.43810592",
// FV
"FV(0.05/12,60,-1000)": "68006.0828408434",
"FV(0.1/4,16,-2000,0,1)": "39729.4608941662",
"FV(0,16,-2000)": "32000",
// FVSCHEDULE
"FVSCHEDULE(10000,A1:A5)": "240000",
"FVSCHEDULE(10000,0.5)": "15000",
// INTRATE
"INTRATE(\"04/01/2005\",\"03/31/2010\",1000,2125)": "0.225",
// IPMT
"IPMT(0.05/12,2,60,50000)": "-205.26988187972",
"IPMT(0.035/4,2,8,0,5000,1)": "5.25745523782908",
// ISPMT
"ISPMT(0.05/12,1,60,50000)": "-204.861111111111",
"ISPMT(0.05/12,2,60,50000)": "-201.388888888889",
"ISPMT(0.05/12,2,1,50000)": "208.333333333333",
// MDURATION
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4)": "6.54355176321876",
// NOMINAL
"NOMINAL(0.025,12)": "0.0247180352381129",
// NPER
"NPER(0.04,-6000,50000)": "10.3380350715077",
"NPER(0,-6000,50000)": "8.33333333333333",
"NPER(0.06/4,-2000,60000,30000,1)": "52.7947737092748",
// NPV
"NPV(0.02,-5000,\"\",800)": "-4133.02575932334",
// ODDFPRICE
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": "107.691830256629",
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,1)": "106.766915010929",
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,3)": "106.7819138147",
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,4)": "106.771913772467",
"ODDFPRICE(\"11/11/2008\",\"03/01/2021\",\"10/15/2008\",\"03/01/2009\",7.85%,6.25%,100,2,1)": "113.597717474079",
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"09/30/2017\",5.5%,3.5%,100,4,0)": "106.72930611878",
"ODDFPRICE(\"11/11/2008\",\"03/29/2021\",\"08/15/2008\",\"03/29/2009\",0.0785,0.0625,100,2,1)": "113.61826640814",
// ODDFYIELD
"ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,1)": "0.0495998049937776",
"ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,2)": "0.0496289417392839",
"ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,4,1)": "0.0464750282973541",
// ODDLPRICE
"ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "5.0517841252892",
"ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "10.3667274303228",
// ODDLYIELD
"ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "0.0451922356291692",
"ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "0.0882287538349037",
// PDURATION
"PDURATION(0.04,10000,15000)": "10.3380350715076",
// PMT
"PMT(0,8,0,5000,1)": "-625",
"PMT(0.035/4,8,0,5000,1)": "-600.852027180466",
// PRICE
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2)": "110.655105178443",
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,4)": "110.655105178443",
"PRICE(\"04/01/2012\",\"03/31/2020\",12%,10%,100,2)": "110.834483593216",
"PRICE(\"01/01/2010\",\"06/30/2010\",0.5,1,1,1,4)": "8.92419088847661",
// PPMT
"PPMT(0.05/12,2,60,50000)": "-738.291800320824",
"PPMT(0.035/4,2,8,0,5000,1)": "-606.109482418295",
// PRICEDISC
"PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100)": "90",
"PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100,3)": "90",
"PRICEDISC(\"42826\",\"03/31/2021\",2.5%,100,3)": "90",
// PRICEMAT
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": "107.170454545455",
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,0)": "107.170454545455",
// PV
"PV(0,60,1000)": "-60000",
"PV(5%/12,60,1000)": "-52990.7063239275",
"PV(10%/4,16,2000,0,1)": "-26762.7554528811",
// RATE
"RATE(60,-1000,50000)": "0.0061834131621292",
"RATE(24,-800,0,20000,1)": "0.00325084350160374",
"RATE(48,-200,8000,3,1,0.5)": "0.0080412665831637",
// RECEIVED
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%)": "1290.32258064516",
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,0)": "1290.32258064516",
// RRI
"RRI(10,10000,15000)": "0.0413797439924106",
// SLN
"SLN(10000,1000,5)": "1800",
// SYD
"SYD(10000,1000,5,1)": "3000",
"SYD(10000,1000,5,2)": "2400",
// TBILLEQ
"TBILLEQ(\"01/01/2017\",\"06/30/2017\",2.5%)": "0.0256680731364276",
// TBILLPRICE
"TBILLPRICE(\"02/01/2017\",\"06/30/2017\",2.75%)": "98.8618055555556",
// TBILLYIELD
"TBILLYIELD(\"02/01/2017\",\"06/30/2017\",99)": "0.024405125076266",
// VDB
"VDB(10000,1000,5,0,1)": "4000",
"VDB(10000,1000,5,1,3)": "3840",
"VDB(10000,1000,5,3,5)": "1160",
"VDB(10000,1000,5,3.5,5,2)": "709.6",
"VDB(10000,1000,5,3,5,0.2,FALSE)": "3600",
"VDB(10000,1000,5,3,5,0.2,TRUE)": "693.633024",
"VDB(24000,3000,10,0,0.875,2)": "4200",
"VDB(24000,3000,10,0.1,1)": "4233.6",
"VDB(24000,3000,10,0.1,1,1)": "2138.4",
"VDB(24000,3000,100,50,100,1)": "10377.2944184652",
"VDB(24000,3000,100,50,100,2)": "5740.0723220908",
// YIELD
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4)": "0.0975631546829798",
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,4)": "0.0976269355643988",
"YIELD(\"01/01/2010\",\"06/30/2010\",0.5,1,1,1,4)": "1.91285866099894",
"YIELD(\"01/01/2010\",\"06/30/2010\",0,1,1,1,4)": "0",
"YIELD(\"01/01/2010\",\"01/02/2020\",100,68.15518653988686,1,1,1)": "64",
// YIELDDISC
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100)": "0.0622012325059031",
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,0)": "0.0622012325059031",
// YIELDMAT
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101)": "0.0419422478838651",
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "0.0419422478838651",
// DISPIMG
"_xlfn.DISPIMG(\"ID_********************************\",1)": "ID_********************************",
}
for formula, expected := range mathCalc {
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
mathCalcError := map[string][]string{
"1/0": {"", "#DIV/0!"},
"1^\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"\"text\"^1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"1+\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"\"text\"+1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"1-\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"\"text\"-1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"1*\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"\"text\"*1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"1/\"text\"": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
"\"text\"/1": {"", "strconv.ParseFloat: parsing \"text\": invalid syntax"},
// Engineering Functions
// BESSELI
"BESSELI()": {"#VALUE!", "BESSELI requires 2 numeric arguments"},
"BESSELI(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELI(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// BESSELJ
"BESSELJ()": {"#VALUE!", "BESSELJ requires 2 numeric arguments"},
"BESSELJ(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELJ(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// BESSELK
"BESSELK()": {"#VALUE!", "BESSELK requires 2 numeric arguments"},
"BESSELK(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELK(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELK(-1,0)": {"#NUM!", "#NUM!"},
"BESSELK(1,-1)": {"#NUM!", "#NUM!"},
// BESSELY
"BESSELY()": {"#VALUE!", "BESSELY requires 2 numeric arguments"},
"BESSELY(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELY(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BESSELY(-1,0)": {"#NUM!", "#NUM!"},
"BESSELY(1,-1)": {"#NUM!", "#NUM!"},
// BIN2DEC
"BIN2DEC()": {"#VALUE!", "BIN2DEC requires 1 numeric argument"},
"BIN2DEC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// BIN2HEX
"BIN2HEX()": {"#VALUE!", "BIN2HEX requires at least 1 argument"},
"BIN2HEX(1,1,1)": {"#VALUE!", "BIN2HEX allows at most 2 arguments"},
"BIN2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BIN2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BIN2HEX(12345678901,10)": {"#NUM!", "#NUM!"},
"BIN2HEX(1,-1)": {"#NUM!", "#NUM!"},
"BIN2HEX(31,1)": {"#NUM!", "#NUM!"},
// BIN2OCT
"BIN2OCT()": {"#VALUE!", "BIN2OCT requires at least 1 argument"},
"BIN2OCT(1,1,1)": {"#VALUE!", "BIN2OCT allows at most 2 arguments"},
"BIN2OCT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BIN2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BIN2OCT(-12345678901 ,10)": {"#NUM!", "#NUM!"},
"BIN2OCT(1,-1)": {"#NUM!", "#NUM!"},
"BIN2OCT(8,1)": {"#NUM!", "#NUM!"},
// BITAND
"BITAND()": {"#VALUE!", "BITAND requires 2 numeric arguments"},
"BITAND(-1,2)": {"#NUM!", "#NUM!"},
"BITAND(2^48,2)": {"#NUM!", "#NUM!"},
"BITAND(1,-1)": {"#NUM!", "#NUM!"},
"BITAND(\"\",-1)": {"#NUM!", "#NUM!"},
"BITAND(1,\"\")": {"#NUM!", "#NUM!"},
"BITAND(1,2^48)": {"#NUM!", "#NUM!"},
// BITLSHIFT
"BITLSHIFT()": {"#VALUE!", "BITLSHIFT requires 2 numeric arguments"},
"BITLSHIFT(-1,2)": {"#NUM!", "#NUM!"},
"BITLSHIFT(2^48,2)": {"#NUM!", "#NUM!"},
"BITLSHIFT(1,-1)": {"#NUM!", "#NUM!"},
"BITLSHIFT(\"\",-1)": {"#NUM!", "#NUM!"},
"BITLSHIFT(1,\"\")": {"#NUM!", "#NUM!"},
"BITLSHIFT(1,2^48)": {"#NUM!", "#NUM!"},
// BITOR
"BITOR()": {"#VALUE!", "BITOR requires 2 numeric arguments"},
"BITOR(-1,2)": {"#NUM!", "#NUM!"},
"BITOR(2^48,2)": {"#NUM!", "#NUM!"},
"BITOR(1,-1)": {"#NUM!", "#NUM!"},
"BITOR(\"\",-1)": {"#NUM!", "#NUM!"},
"BITOR(1,\"\")": {"#NUM!", "#NUM!"},
"BITOR(1,2^48)": {"#NUM!", "#NUM!"},
// BITRSHIFT
"BITRSHIFT()": {"#VALUE!", "BITRSHIFT requires 2 numeric arguments"},
"BITRSHIFT(-1,2)": {"#NUM!", "#NUM!"},
"BITRSHIFT(2^48,2)": {"#NUM!", "#NUM!"},
"BITRSHIFT(1,-1)": {"#NUM!", "#NUM!"},
"BITRSHIFT(\"\",-1)": {"#NUM!", "#NUM!"},
"BITRSHIFT(1,\"\")": {"#NUM!", "#NUM!"},
"BITRSHIFT(1,2^48)": {"#NUM!", "#NUM!"},
// BITXOR
"BITXOR()": {"#VALUE!", "BITXOR requires 2 numeric arguments"},
"BITXOR(-1,2)": {"#NUM!", "#NUM!"},
"BITXOR(2^48,2)": {"#NUM!", "#NUM!"},
"BITXOR(1,-1)": {"#NUM!", "#NUM!"},
"BITXOR(\"\",-1)": {"#NUM!", "#NUM!"},
"BITXOR(1,\"\")": {"#NUM!", "#NUM!"},
"BITXOR(1,2^48)": {"#NUM!", "#NUM!"},
// COMPLEX
"COMPLEX()": {"#VALUE!", "COMPLEX requires at least 2 arguments"},
"COMPLEX(10,-5,\"\")": {"#VALUE!", "#VALUE!"},
"COMPLEX(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"COMPLEX(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"COMPLEX(10,-5,\"i\",0)": {"#VALUE!", "COMPLEX allows at most 3 arguments"},
// CONVERT
"CONVERT()": {"#VALUE!", "CONVERT requires 3 arguments"},
"CONVERT(\"\",\"m\",\"yd\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONVERT(20.2,\"m\",\"C\")": {"#N/A", "#N/A"},
"CONVERT(20.2,\"\",\"C\")": {"#N/A", "#N/A"},
"CONVERT(100,\"dapt\",\"pt\")": {"#N/A", "#N/A"},
"CONVERT(1,\"ft\",\"day\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"kpt\",\"lt\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"lt\",\"kpt\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"kiqt\",\"pt\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"pt\",\"kiqt\")": {"#N/A", "#N/A"},
"CONVERT(12345.6,\"baton\",\"cwt\")": {"#N/A", "#N/A"},
"CONVERT(12345.6,\"cwt\",\"baton\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"xxxx\",\"m\")": {"#N/A", "#N/A"},
"CONVERT(234.56,\"m\",\"xxxx\")": {"#N/A", "#N/A"},
// DEC2BIN
"DEC2BIN()": {"#VALUE!", "DEC2BIN requires at least 1 argument"},
"DEC2BIN(1,1,1)": {"#VALUE!", "DEC2BIN allows at most 2 arguments"},
"DEC2BIN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2BIN(-513,10)": {"#NUM!", "#NUM!"},
"DEC2BIN(1,-1)": {"#NUM!", "#NUM!"},
"DEC2BIN(2,1)": {"#NUM!", "#NUM!"},
// DEC2HEX
"DEC2HEX()": {"#VALUE!", "DEC2HEX requires at least 1 argument"},
"DEC2HEX(1,1,1)": {"#VALUE!", "DEC2HEX allows at most 2 arguments"},
"DEC2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2HEX(-549755813888,10)": {"#NUM!", "#NUM!"},
"DEC2HEX(1,-1)": {"#NUM!", "#NUM!"},
"DEC2HEX(31,1)": {"#NUM!", "#NUM!"},
// DEC2OCT
"DEC2OCT()": {"#VALUE!", "DEC2OCT requires at least 1 argument"},
"DEC2OCT(1,1,1)": {"#VALUE!", "DEC2OCT allows at most 2 arguments"},
"DEC2OCT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DEC2OCT(-536870912 ,10)": {"#NUM!", "#NUM!"},
"DEC2OCT(1,-1)": {"#NUM!", "#NUM!"},
"DEC2OCT(8,1)": {"#NUM!", "#NUM!"},
// DELTA
"DELTA()": {"#VALUE!", "DELTA requires at least 1 argument"},
"DELTA(0,0,0)": {"#VALUE!", "DELTA allows at most 2 arguments"},
"DELTA(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DELTA(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ERF
"ERF()": {"#VALUE!", "ERF requires at least 1 argument"},
"ERF(0,0,0)": {"#VALUE!", "ERF allows at most 2 arguments"},
"ERF(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ERF(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ERF.PRECISE
"ERF.PRECISE()": {"#VALUE!", "ERF.PRECISE requires 1 argument"},
"ERF.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ERFC
"ERFC()": {"#VALUE!", "ERFC requires 1 argument"},
"ERFC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ERFC.PRECISE
"ERFC.PRECISE()": {"#VALUE!", "ERFC.PRECISE requires 1 argument"},
"ERFC.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// GESTEP
"GESTEP()": {"#VALUE!", "GESTEP requires at least 1 argument"},
"GESTEP(0,0,0)": {"#VALUE!", "GESTEP allows at most 2 arguments"},
"GESTEP(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GESTEP(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// HEX2BIN
"HEX2BIN()": {"#VALUE!", "HEX2BIN requires at least 1 argument"},
"HEX2BIN(1,1,1)": {"#VALUE!", "HEX2BIN allows at most 2 arguments"},
"HEX2BIN(\"X\",1)": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
"HEX2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HEX2BIN(-513,10)": {"#NUM!", "strconv.ParseInt: parsing \"-\": invalid syntax"},
"HEX2BIN(1,-1)": {"#NUM!", "#NUM!"},
"HEX2BIN(2,1)": {"#NUM!", "#NUM!"},
// HEX2DEC
"HEX2DEC()": {"#VALUE!", "HEX2DEC requires 1 numeric argument"},
"HEX2DEC(\"X\")": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
// HEX2OCT
"HEX2OCT()": {"#VALUE!", "HEX2OCT requires at least 1 argument"},
"HEX2OCT(1,1,1)": {"#VALUE!", "HEX2OCT allows at most 2 arguments"},
"HEX2OCT(\"X\",1)": {"#NUM!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
"HEX2OCT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HEX2OCT(-513,10)": {"#NUM!", "strconv.ParseInt: parsing \"-\": invalid syntax"},
"HEX2OCT(1,-1)": {"#NUM!", "#NUM!"},
// IMABS
"IMABS()": {"#VALUE!", "IMABS requires 1 argument"},
"IMABS(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMAGINARY
"IMAGINARY()": {"#VALUE!", "IMAGINARY requires 1 argument"},
"IMAGINARY(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMARGUMENT
"IMARGUMENT()": {"#VALUE!", "IMARGUMENT requires 1 argument"},
"IMARGUMENT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMCONJUGATE
"IMCONJUGATE()": {"#VALUE!", "IMCONJUGATE requires 1 argument"},
"IMCONJUGATE(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMCOS
"IMCOS()": {"#VALUE!", "IMCOS requires 1 argument"},
"IMCOS(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMCOSH
"IMCOSH()": {"#VALUE!", "IMCOSH requires 1 argument"},
"IMCOSH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMCOT
"IMCOT()": {"#VALUE!", "IMCOT requires 1 argument"},
"IMCOT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMCSC
"IMCSC()": {"#VALUE!", "IMCSC requires 1 argument"},
"IMCSC(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMCSC(0)": {"#NUM!", "#NUM!"},
// IMCSCH
"IMCSCH()": {"#VALUE!", "IMCSCH requires 1 argument"},
"IMCSCH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMCSCH(0)": {"#NUM!", "#NUM!"},
// IMDIV
"IMDIV()": {"#VALUE!", "IMDIV requires 2 arguments"},
"IMDIV(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMDIV(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMDIV(1,0)": {"#NUM!", "#NUM!"},
// IMEXP
"IMEXP()": {"#VALUE!", "IMEXP requires 1 argument"},
"IMEXP(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMLN
"IMLN()": {"#VALUE!", "IMLN requires 1 argument"},
"IMLN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMLN(0)": {"#NUM!", "#NUM!"},
// IMLOG10
"IMLOG10()": {"#VALUE!", "IMLOG10 requires 1 argument"},
"IMLOG10(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMLOG10(0)": {"#NUM!", "#NUM!"},
// IMLOG2
"IMLOG2()": {"#VALUE!", "IMLOG2 requires 1 argument"},
"IMLOG2(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMLOG2(0)": {"#NUM!", "#NUM!"},
// IMPOWER
"IMPOWER()": {"#VALUE!", "IMPOWER requires 2 arguments"},
"IMPOWER(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMPOWER(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMPOWER(0,0)": {"#NUM!", "#NUM!"},
"IMPOWER(0,-1)": {"#NUM!", "#NUM!"},
// IMPRODUCT
"IMPRODUCT(\"x\")": {"#NUM!", "strconv.ParseComplex: parsing \"x\": invalid syntax"},
"IMPRODUCT(A1:D1)": {"#NUM!", "strconv.ParseComplex: parsing \"Month\": invalid syntax"},
// IMREAL
"IMREAL()": {"#VALUE!", "IMREAL requires 1 argument"},
"IMREAL(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSEC
"IMSEC()": {"#VALUE!", "IMSEC requires 1 argument"},
"IMSEC(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSECH
"IMSECH()": {"#VALUE!", "IMSECH requires 1 argument"},
"IMSECH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSIN
"IMSIN()": {"#VALUE!", "IMSIN requires 1 argument"},
"IMSIN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSINH
"IMSINH()": {"#VALUE!", "IMSINH requires 1 argument"},
"IMSINH(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSQRT
"IMSQRT()": {"#VALUE!", "IMSQRT requires 1 argument"},
"IMSQRT(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSUB
"IMSUB()": {"#VALUE!", "IMSUB requires 2 arguments"},
"IMSUB(0,\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
"IMSUB(\"\",0)": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMSUM
"IMSUM()": {"#VALUE!", "IMSUM requires at least 1 argument"},
"IMSUM(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// IMTAN
"IMTAN()": {"#VALUE!", "IMTAN requires 1 argument"},
"IMTAN(\"\")": {"#NUM!", "strconv.ParseComplex: parsing \"\": invalid syntax"},
// OCT2BIN
"OCT2BIN()": {"#VALUE!", "OCT2BIN requires at least 1 argument"},
"OCT2BIN(1,1,1)": {"#VALUE!", "OCT2BIN allows at most 2 arguments"},
"OCT2BIN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"OCT2BIN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"OCT2BIN(-536870912 ,10)": {"#NUM!", "#NUM!"},
"OCT2BIN(1,-1)": {"#NUM!", "#NUM!"},
// OCT2DEC
"OCT2DEC()": {"#VALUE!", "OCT2DEC requires 1 numeric argument"},
"OCT2DEC(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// OCT2HEX
"OCT2HEX()": {"#VALUE!", "OCT2HEX requires at least 1 argument"},
"OCT2HEX(1,1,1)": {"#VALUE!", "OCT2HEX allows at most 2 arguments"},
"OCT2HEX(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"OCT2HEX(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"OCT2HEX(-536870912 ,10)": {"#NUM!", "#NUM!"},
"OCT2HEX(1,-1)": {"#NUM!", "#NUM!"},
// Math and Trigonometric Functions
// ABS
"ABS()": {"#VALUE!", "ABS requires 1 numeric argument"},
"ABS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ABS(~)": {"#NAME?", "invalid reference"},
// ACOS
"ACOS()": {"#VALUE!", "ACOS requires 1 numeric argument"},
"ACOS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ACOS(ACOS(0))": {"#NUM!", "#NUM!"},
// ACOSH
"ACOSH()": {"#VALUE!", "ACOSH requires 1 numeric argument"},
"ACOSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.ACOT
"_xlfn.ACOT()": {"#VALUE!", "ACOT requires 1 numeric argument"},
"_xlfn.ACOT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.ACOTH
"_xlfn.ACOTH()": {"#VALUE!", "ACOTH requires 1 numeric argument"},
"_xlfn.ACOTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.ACOTH(_xlfn.ACOTH(2))": {"#NUM!", "#NUM!"},
// _xlfn.AGGREGATE
"_xlfn.AGGREGATE()": {"#VALUE!", "AGGREGATE requires at least 3 arguments"},
"_xlfn.AGGREGATE(\"\",0,A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"_xlfn.AGGREGATE(1,\"\",A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"_xlfn.AGGREGATE(0,A4:A5)": {"#VALUE!", "AGGREGATE has invalid function_num"},
"_xlfn.AGGREGATE(1,8,A4:A5)": {"#VALUE!", "AGGREGATE has invalid options"},
"_xlfn.AGGREGATE(1,0,A5:A6)": {"#DIV/0!", "#DIV/0!"},
"_xlfn.AGGREGATE(13,0,A1:A6)": {"#N/A", "#N/A"},
"_xlfn.AGGREGATE(18,0,A1:A6,1)": {"#NUM!", "#NUM!"},
// _xlfn.ARABIC
"_xlfn.ARABIC()": {"#VALUE!", "ARABIC requires 1 numeric argument"},
"_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": {"#VALUE!", "#VALUE!"},
// ASIN
"ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"},
"ASIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ASINH
"ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"},
"ASINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN
"ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"},
"ATAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATANH
"ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"},
"ATANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN2
"ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"},
"ATAN2(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ATAN2(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// BASE
"BASE()": {"#VALUE!", "BASE requires at least 2 arguments"},
"BASE(1,2,3,4)": {"#VALUE!", "BASE allows at most 3 arguments"},
"BASE(1,1)": {"#VALUE!", "radix must be an integer >= 2 and <= 36"},
"BASE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"BASE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"BASE(1,2,\"X\")": {"#VALUE!", "strconv.Atoi: parsing \"X\": invalid syntax"},
// CEILING
"CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"},
"CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"},
"CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"},
"CEILING(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"CEILING(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.MATH
"_xlfn.CEILING.MATH()": {"#VALUE!", "CEILING.MATH requires at least 1 argument"},
"_xlfn.CEILING.MATH(1,2,3,4)": {"#VALUE!", "CEILING.MATH allows at most 3 arguments"},
"_xlfn.CEILING.MATH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.CEILING.MATH(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.CEILING.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.PRECISE
"_xlfn.CEILING.PRECISE()": {"#VALUE!", "CEILING.PRECISE requires at least 1 argument"},
"_xlfn.CEILING.PRECISE(1,2,3)": {"#VALUE!", "CEILING.PRECISE allows at most 2 arguments"},
"_xlfn.CEILING.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.CEILING.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COMBIN
"COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"},
"COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
"COMBIN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"COMBIN(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COMBINA
"_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"},
"_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"},
"_xlfn.COMBINA(-1,-1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
"_xlfn.COMBINA(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.COMBINA(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COS
"COS()": {"#VALUE!", "COS requires 1 numeric argument"},
"COS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COSH
"COSH()": {"#VALUE!", "COSH requires 1 numeric argument"},
"COSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COT
"COT()": {"#VALUE!", "COT requires 1 numeric argument"},
"COT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"COT(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.COTH
"COTH()": {"#VALUE!", "COTH requires 1 numeric argument"},
"COTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"COTH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSC
"_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"},
"_xlfn.CSC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSCH
"_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"},
"_xlfn.CSCH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.DECIMAL
"_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"},
"_xlfn.DECIMAL(\"X\",2)": {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
"_xlfn.DECIMAL(2000,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// DEGREES
"DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"},
"DEGREES(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"DEGREES(0)": {"#DIV/0!", "#DIV/0!"},
// EVEN
"EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"},
"EVEN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// EXP
"EXP()": {"#VALUE!", "EXP requires 1 numeric argument"},
"EXP(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// FACT
"FACT()": {"#VALUE!", "FACT requires 1 numeric argument"},
"FACT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"FACT(-1)": {"#NUM!", "#NUM!"},
// FACTDOUBLE
"FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"},
"FACTDOUBLE(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"FACTDOUBLE(-1)": {"#NUM!", "#NUM!"},
// FLOOR
"FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"},
"FLOOR(\"X\",-1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"FLOOR(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"FLOOR(1,-1)": {"#NUM!", "invalid arguments to FLOOR"},
// _xlfn.FLOOR.MATH
"_xlfn.FLOOR.MATH()": {"#VALUE!", "FLOOR.MATH requires at least 1 argument"},
"_xlfn.FLOOR.MATH(1,2,3,4)": {"#VALUE!", "FLOOR.MATH allows at most 3 arguments"},
"_xlfn.FLOOR.MATH(\"X\",2,3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.FLOOR.MATH(1,\"X\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.FLOOR.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.FLOOR.PRECISE
"_xlfn.FLOOR.PRECISE()": {"#VALUE!", "FLOOR.PRECISE requires at least 1 argument"},
"_xlfn.FLOOR.PRECISE(1,2,3)": {"#VALUE!", "FLOOR.PRECISE allows at most 2 arguments"},
"_xlfn.FLOOR.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.FLOOR.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// GCD
"GCD()": {"#VALUE!", "GCD requires at least 1 argument"},
"GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"GCD(1,-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"GCD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// INT
"INT()": {"#VALUE!", "INT requires 1 numeric argument"},
"INT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ISO.CEILING
"ISO.CEILING()": {"#VALUE!", "ISO.CEILING requires at least 1 argument"},
"ISO.CEILING(1,2,3)": {"#VALUE!", "ISO.CEILING allows at most 2 arguments"},
"ISO.CEILING(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ISO.CEILING(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LCM
"LCM()": {"#VALUE!", "LCM requires at least 1 argument"},
"LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"LCM(1,-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"LCM(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LN
"LN()": {"#VALUE!", "LN requires 1 numeric argument"},
"LN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LOG
"LOG()": {"#VALUE!", "LOG requires at least 1 argument"},
"LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"},
"LOG(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"LOG(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"LOG(0,0)": {"#NUM!", "#DIV/0!"},
"LOG(1,0)": {"#NUM!", "#DIV/0!"},
"LOG(1,1)": {"#DIV/0!", "#DIV/0!"},
// LOG10
"LOG10()": {"#VALUE!", "LOG10 requires 1 numeric argument"},
"LOG10(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MDETERM
"MDETERM()": {"#VALUE!", "MDETERM requires 1 argument"},
// MINVERSE
"MINVERSE()": {"#VALUE!", "MINVERSE requires 1 argument"},
"MINVERSE(B3:C4)": {"#VALUE!", "#VALUE!"},
"MINVERSE(A1:C2)": {"#VALUE!", "#VALUE!"},
"MINVERSE(A4:A4)": {"#NUM!", "#NUM!"},
// MMULT
"MMULT()": {"#VALUE!", "MMULT requires 2 argument"},
"MMULT(A1:B2,B3:C4)": {"#VALUE!", "#VALUE!"},
"MMULT(B3:C4,A1:B2)": {"#VALUE!", "#VALUE!"},
"MMULT(A1:A2,B1:B2)": {"#VALUE!", "#VALUE!"},
// MOD
"MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"},
"MOD(6,0)": {"#DIV/0!", "MOD divide by zero"},
"MOD(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"MOD(6,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MROUND
"MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"},
"MROUND(1,0)": {"#NUM!", "#NUM!"},
"MROUND(1,-1)": {"#NUM!", "#NUM!"},
"MROUND(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"MROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MULTINOMIAL
"MULTINOMIAL(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.MUNIT
"_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"},
"_xlfn.MUNIT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"_xlfn.MUNIT(-1)": {"#VALUE!", ""},
// ODD
"ODD()": {"#VALUE!", "ODD requires 1 numeric argument"},
"ODD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// PI
"PI(1)": {"#VALUE!", "PI accepts no arguments"},
// POWER
"POWER(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"POWER(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"POWER(0,0)": {"#NUM!", "#NUM!"},
"POWER(0,-1)": {"#DIV/0!", "#DIV/0!"},
"POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"},
// PRODUCT
"PRODUCT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"PRODUCT(\"\",3,6)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// QUOTIENT
"QUOTIENT(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"QUOTIENT(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"QUOTIENT(1,0)": {"#DIV/0!", "#DIV/0!"},
"QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"},
// RADIANS
"RADIANS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"},
// RAND
"RAND(1)": {"#VALUE!", "RAND accepts no arguments"},
// RANDBETWEEN
"RANDBETWEEN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"RANDBETWEEN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"RANDBETWEEN()": {"#VALUE!", "RANDBETWEEN requires 2 numeric arguments"},
"RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"},
// ROMAN
"ROMAN()": {"#VALUE!", "ROMAN requires at least 1 argument"},
"ROMAN(1,2,3)": {"#VALUE!", "ROMAN allows at most 2 arguments"},
"ROMAN(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ROMAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ROMAN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ROUND
"ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"},
"ROUND(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDDOWN
"ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"},
"ROUNDDOWN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ROUNDDOWN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDUP
"ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"},
"ROUNDUP(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"ROUNDUP(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SEARCH
"SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"},
"SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"},
"SEARCH(2,A1)": {"#VALUE!", "#VALUE!"},
"SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// SEARCHB
"SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"},
"SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"},
"SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"},
"SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"},
"SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// SEC
"_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
"_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.SECH
"_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"},
"_xlfn.SECH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SERIESSUM
"SERIESSUM()": {"#VALUE!", "SERIESSUM requires 4 arguments"},
"SERIESSUM(\"\",2,3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SERIESSUM(1,\"\",3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SERIESSUM(1,2,\"\",A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SERIESSUM(1,2,3,A1:D1)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"},
// SIGN
"SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"},
"SIGN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SIN
"SIN()": {"#VALUE!", "SIN requires 1 numeric argument"},
"SIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SINH
"SINH()": {"#VALUE!", "SINH requires 1 numeric argument"},
"SINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SQRT
"SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"},
"SQRT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SQRT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"SQRT(-1)": {"#NUM!", "#NUM!"},
// SQRTPI
"SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"},
"SQRTPI(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// STDEV
"STDEV()": {"#VALUE!", "STDEV requires at least 1 argument"},
"STDEV(E2:E9)": {"#DIV/0!", "#DIV/0!"},
// STDEV.S
"STDEV.S()": {"#VALUE!", "STDEV.S requires at least 1 argument"},
// STDEVA
"STDEVA()": {"#VALUE!", "STDEVA requires at least 1 argument"},
"STDEVA(E2:E9)": {"#DIV/0!", "#DIV/0!"},
// POISSON.DIST
"POISSON.DIST()": {"#VALUE!", "POISSON.DIST requires 3 arguments"},
// POISSON
"POISSON()": {"#VALUE!", "POISSON requires 3 arguments"},
"POISSON(\"\",0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"POISSON(0,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"POISSON(0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"POISSON(0,-1,TRUE)": {"#N/A", "#N/A"},
// PROB
"PROB()": {"#VALUE!", "PROB requires at least 3 arguments"},
"PROB(A1:A2,B1:B2,1,1,1)": {"#VALUE!", "PROB requires at most 4 arguments"},
"PROB(A1:A2,B1:B2,\"\")": {"#VALUE!", "#VALUE!"},
"PROB(A1:A2,B1:B2,1,\"\")": {"#VALUE!", "#VALUE!"},
"PROB(A1,B1,1)": {"#NUM!", "#NUM!"},
"PROB(A1:A2,B1:B3,1)": {"#N/A", "#N/A"},
"PROB(A1:A2,B1:C2,1)": {"#N/A", "#N/A"},
"PROB(A1:A2,B1:B2,1)": {"#NUM!", "#NUM!"},
// SUBTOTAL
"SUBTOTAL()": {"#VALUE!", "SUBTOTAL requires at least 2 arguments"},
"SUBTOTAL(\"\",A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SUBTOTAL(0,A4:A5)": {"#VALUE!", "SUBTOTAL has invalid function_num"},
"SUBTOTAL(1,A5:A6)": {"#DIV/0!", "#DIV/0!"},
// SUM
"SUM((": {"", ErrInvalidFormula.Error()},
"SUM(-)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()},
"SUM(1+)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()},
"SUM(1-)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()},
"SUM(1*)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()},
"SUM(1/)": {ErrInvalidFormula.Error(), ErrInvalidFormula.Error()},
"SUM(1*SUM(1/0))": {"#DIV/0!", "#DIV/0!"},
"SUM(1*SUM(1/0)*1)": {"", "#DIV/0!"},
"SUM(0:2)": {"#NAME?", "invalid reference"},
"SUM(1:1048577)": {"#NAME?", "invalid reference"},
// SUMIF
"SUMIF()": {"#VALUE!", "SUMIF requires at least 2 arguments"},
// SUMSQ
"SUMSQ(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"SUMSQ(C1:D2)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"},
// SUMPRODUCT
"SUMPRODUCT()": {"#VALUE!", "SUMPRODUCT requires at least 1 argument"},
"SUMPRODUCT(A1,B1:B2)": {"#VALUE!", "#VALUE!"},
"SUMPRODUCT(A1,D1)": {"#VALUE!", "#VALUE!"},
"SUMPRODUCT(A1:A3,D1:D3)": {"#VALUE!", "#VALUE!"},
"SUMPRODUCT(A1:A2,B1:B3)": {"#VALUE!", "#VALUE!"},
"SUMPRODUCT(\"\")": {"#VALUE!", "#VALUE!"},
"SUMPRODUCT(A1,NA())": {"#N/A", "#N/A"},
// SUMX2MY2
"SUMX2MY2()": {"#VALUE!", "SUMX2MY2 requires 2 arguments"},
"SUMX2MY2(A1,B1:B2)": {"#N/A", "#N/A"},
// SUMX2PY2
"SUMX2PY2()": {"#VALUE!", "SUMX2PY2 requires 2 arguments"},
"SUMX2PY2(A1,B1:B2)": {"#N/A", "#N/A"},
// SUMXMY2
"SUMXMY2()": {"#VALUE!", "SUMXMY2 requires 2 arguments"},
"SUMXMY2(A1,B1:B2)": {"#N/A", "#N/A"},
// TAN
"TAN()": {"#VALUE!", "TAN requires 1 numeric argument"},
"TAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// TANH
"TANH()": {"#VALUE!", "TANH requires 1 numeric argument"},
"TANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// TRUNC
"TRUNC()": {"#VALUE!", "TRUNC requires at least 1 argument"},
"TRUNC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"TRUNC(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// Statistical Functions
// AVEDEV
"AVEDEV()": {"#VALUE!", "AVEDEV requires at least 1 argument"},
"AVEDEV(\"\")": {"#VALUE!", "#VALUE!"},
"AVEDEV(1,\"\")": {"#VALUE!", "#VALUE!"},
// AVERAGE
"AVERAGE(H1)": {"#DIV/0!", "#DIV/0!"},
// AVERAGEA
"AVERAGEA(H1)": {"#DIV/0!", "#DIV/0!"},
// AVERAGEIF
"AVERAGEIF()": {"#VALUE!", "AVERAGEIF requires at least 2 arguments"},
"AVERAGEIF(H1,\"\")": {"#DIV/0!", "#DIV/0!"},
"AVERAGEIF(D1:D3,\"Month\",D1:D3)": {"#DIV/0!", "#DIV/0!"},
"AVERAGEIF(C1:C3,\"Month\",D1:D3)": {"#DIV/0!", "#DIV/0!"},
// BETA.DIST
"BETA.DIST()": {"#VALUE!", "BETA.DIST requires at least 4 arguments"},
"BETA.DIST(0.4,4,5,TRUE,0,1,0)": {"#VALUE!", "BETA.DIST requires at most 6 arguments"},
"BETA.DIST(\"\",4,5,TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,\"\",5,TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,4,\"\",TRUE,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,4,5,\"\",0,1)": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,4,5,TRUE,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,4,5,TRUE,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.DIST(0.4,0,5,TRUE,0,1)": {"#NUM!", "#NUM!"},
"BETA.DIST(0.4,4,0,TRUE,0,0)": {"#NUM!", "#NUM!"},
"BETA.DIST(0.4,4,5,TRUE,0.5,1)": {"#NUM!", "#NUM!"},
"BETA.DIST(0.4,4,5,TRUE,0,0.3)": {"#NUM!", "#NUM!"},
"BETA.DIST(0.4,4,5,TRUE,0.4,0.4)": {"#NUM!", "#NUM!"},
// BETADIST
"BETADIST()": {"#VALUE!", "BETADIST requires at least 3 arguments"},
"BETADIST(0.4,4,5,0,1,0)": {"#VALUE!", "BETADIST requires at most 5 arguments"},
"BETADIST(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETADIST(0.4,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETADIST(0.4,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETADIST(0.4,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETADIST(0.4,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETADIST(2,4,5,3,1)": {"#NUM!", "#NUM!"},
"BETADIST(2,4,5,0,1)": {"#NUM!", "#NUM!"},
"BETADIST(0.4,0,5,0,1)": {"#NUM!", "#NUM!"},
"BETADIST(0.4,4,0,0,1)": {"#NUM!", "#NUM!"},
"BETADIST(0.4,4,5,0.4,0.4)": {"#NUM!", "#NUM!"},
// BETAINV
"BETAINV()": {"#VALUE!", "BETAINV requires at least 3 arguments"},
"BETAINV(0.2,4,5,0,1,0)": {"#VALUE!", "BETAINV requires at most 5 arguments"},
"BETAINV(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETAINV(0.2,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETAINV(0.2,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETAINV(0.2,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETAINV(0.2,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETAINV(0,4,5,0,1)": {"#NUM!", "#NUM!"},
"BETAINV(1,4,5,0,1)": {"#NUM!", "#NUM!"},
"BETAINV(0.2,0,5,0,1)": {"#NUM!", "#NUM!"},
"BETAINV(0.2,4,0,0,1)": {"#NUM!", "#NUM!"},
"BETAINV(0.2,4,5,2,2)": {"#NUM!", "#NUM!"},
// BETA.INV
"BETA.INV()": {"#VALUE!", "BETA.INV requires at least 3 arguments"},
"BETA.INV(0.2,4,5,0,1,0)": {"#VALUE!", "BETA.INV requires at most 5 arguments"},
"BETA.INV(\"\",4,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.INV(0.2,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.INV(0.2,4,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.INV(0.2,4,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.INV(0.2,4,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BETA.INV(0,4,5,0,1)": {"#NUM!", "#NUM!"},
"BETA.INV(1,4,5,0,1)": {"#NUM!", "#NUM!"},
"BETA.INV(0.2,0,5,0,1)": {"#NUM!", "#NUM!"},
"BETA.INV(0.2,4,0,0,1)": {"#NUM!", "#NUM!"},
"BETA.INV(0.2,4,5,2,2)": {"#NUM!", "#NUM!"},
// BINOMDIST
"BINOMDIST()": {"#VALUE!", "BINOMDIST requires 4 arguments"},
"BINOMDIST(\"\",100,0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOMDIST(10,\"\",0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOMDIST(10,100,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOMDIST(10,100,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"BINOMDIST(-1,100,0.5,FALSE)": {"#NUM!", "#NUM!"},
"BINOMDIST(110,100,0.5,FALSE)": {"#NUM!", "#NUM!"},
"BINOMDIST(10,100,-1,FALSE)": {"#NUM!", "#NUM!"},
"BINOMDIST(10,100,2,FALSE)": {"#NUM!", "#NUM!"},
// BINOM.DIST
"BINOM.DIST()": {"#VALUE!", "BINOM.DIST requires 4 arguments"},
"BINOM.DIST(\"\",100,0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST(10,\"\",0.5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST(10,100,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST(10,100,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"BINOM.DIST(-1,100,0.5,FALSE)": {"#NUM!", "#NUM!"},
"BINOM.DIST(110,100,0.5,FALSE)": {"#NUM!", "#NUM!"},
"BINOM.DIST(10,100,-1,FALSE)": {"#NUM!", "#NUM!"},
"BINOM.DIST(10,100,2,FALSE)": {"#NUM!", "#NUM!"},
// BINOM.DIST.RANGE
"BINOM.DIST.RANGE()": {"#VALUE!", "BINOM.DIST.RANGE requires at least 3 arguments"},
"BINOM.DIST.RANGE(100,0.5,0,40,0)": {"#VALUE!", "BINOM.DIST.RANGE requires at most 4 arguments"},
"BINOM.DIST.RANGE(\"\",0.5,0,40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST.RANGE(100,\"\",0,40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST.RANGE(100,0.5,\"\",40)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST.RANGE(100,0.5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.DIST.RANGE(100,-1,0,40)": {"#NUM!", "#NUM!"},
"BINOM.DIST.RANGE(100,2,0,40)": {"#NUM!", "#NUM!"},
"BINOM.DIST.RANGE(100,0.5,-1,40)": {"#NUM!", "#NUM!"},
"BINOM.DIST.RANGE(100,0.5,110,40)": {"#NUM!", "#NUM!"},
"BINOM.DIST.RANGE(100,0.5,0,-1)": {"#NUM!", "#NUM!"},
"BINOM.DIST.RANGE(100,0.5,0,110)": {"#NUM!", "#NUM!"},
// BINOM.INV
"BINOM.INV()": {"#VALUE!", "BINOM.INV requires 3 numeric arguments"},
"BINOM.INV(\"\",0.5,20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.INV(100,\"\",20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.INV(100,0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"BINOM.INV(-1,0.5,20%)": {"#NUM!", "#NUM!"},
"BINOM.INV(100,-1,20%)": {"#NUM!", "#NUM!"},
"BINOM.INV(100,2,20%)": {"#NUM!", "#NUM!"},
"BINOM.INV(100,0.5,-1)": {"#NUM!", "#NUM!"},
"BINOM.INV(100,0.5,2)": {"#NUM!", "#NUM!"},
"BINOM.INV(1,1,20%)": {"#NUM!", "#NUM!"},
// CHIDIST
"CHIDIST()": {"#VALUE!", "CHIDIST requires 2 numeric arguments"},
"CHIDIST(\"\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHIDIST(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// CHIINV
"CHIINV()": {"#VALUE!", "CHIINV requires 2 numeric arguments"},
"CHIINV(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHIINV(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHIINV(0,1)": {"#NUM!", "#NUM!"},
"CHIINV(2,1)": {"#NUM!", "#NUM!"},
"CHIINV(0.5,0.5)": {"#NUM!", "#NUM!"},
// CHISQ.DIST
"CHISQ.DIST()": {"#VALUE!", "CHISQ.DIST requires 3 arguments"},
"CHISQ.DIST(\"\",2,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.DIST(3,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.DIST(3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"CHISQ.DIST(-1,2,TRUE)": {"#NUM!", "#NUM!"},
"CHISQ.DIST(3,0,TRUE)": {"#NUM!", "#NUM!"},
// CHISQ.DIST.RT
"CHISQ.DIST.RT()": {"#VALUE!", "CHISQ.DIST.RT requires 2 numeric arguments"},
"CHISQ.DIST.RT(\"\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.DIST.RT(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// CHISQ.INV
"CHISQ.INV()": {"#VALUE!", "CHISQ.INV requires 2 numeric arguments"},
"CHISQ.INV(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.INV(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.INV(-1,1)": {"#NUM!", "#NUM!"},
"CHISQ.INV(1,1)": {"#NUM!", "#NUM!"},
"CHISQ.INV(0.5,0.5)": {"#NUM!", "#NUM!"},
"CHISQ.INV(0.5,10000000001)": {"#NUM!", "#NUM!"},
// CHISQ.INV.RT
"CHISQ.INV.RT()": {"#VALUE!", "CHISQ.INV.RT requires 2 numeric arguments"},
"CHISQ.INV.RT(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.INV.RT(0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CHISQ.INV.RT(0,1)": {"#NUM!", "#NUM!"},
"CHISQ.INV.RT(2,1)": {"#NUM!", "#NUM!"},
"CHISQ.INV.RT(0.5,0.5)": {"#NUM!", "#NUM!"},
// CONFIDENCE
"CONFIDENCE()": {"#VALUE!", "CONFIDENCE requires 3 numeric arguments"},
"CONFIDENCE(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE(0,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE(1,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE(0.05,0,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE(0.05,0.07,0.5)": {"#NUM!", "#NUM!"},
// CONFIDENCE.NORM
"CONFIDENCE.NORM()": {"#VALUE!", "CONFIDENCE.NORM requires 3 numeric arguments"},
"CONFIDENCE.NORM(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.NORM(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.NORM(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.NORM(0,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.NORM(1,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.NORM(0.05,0,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.NORM(0.05,0.07,0.5)": {"#NUM!", "#NUM!"},
// CORREL
"CORREL()": {"#VALUE!", "CORREL requires 2 arguments"},
"CORREL(A1:A3,B1:B5)": {"#N/A", "#N/A"},
"CORREL(A1:A1,B1:B1)": {"#DIV/0!", "#DIV/0!"},
// CONFIDENCE.T
"CONFIDENCE.T()": {"#VALUE!", "CONFIDENCE.T requires 3 arguments"},
"CONFIDENCE.T(\"\",0.07,100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.T(0.05,\"\",100)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.T(0.05,0.07,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CONFIDENCE.T(0,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.T(1,0.07,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.T(0.05,0,100)": {"#NUM!", "#NUM!"},
"CONFIDENCE.T(0.05,0.07,0)": {"#NUM!", "#NUM!"},
"CONFIDENCE.T(0.05,0.07,1)": {"#DIV/0!", "#DIV/0!"},
// COUNTBLANK
"COUNTBLANK()": {"#VALUE!", "COUNTBLANK requires 1 argument"},
"COUNTBLANK(1,2)": {"#VALUE!", "COUNTBLANK requires 1 argument"},
// COUNTIF
"COUNTIF()": {"#VALUE!", "COUNTIF requires 2 arguments"},
// COUNTIFS
"COUNTIFS()": {"#VALUE!", "COUNTIFS requires at least 2 arguments"},
"COUNTIFS(A1:A9,2,D1:D9)": {"#N/A", "#N/A"},
// CRITBINOM
"CRITBINOM()": {"#VALUE!", "CRITBINOM requires 3 numeric arguments"},
"CRITBINOM(\"\",0.5,20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CRITBINOM(100,\"\",20%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CRITBINOM(100,0.5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CRITBINOM(-1,0.5,20%)": {"#NUM!", "#NUM!"},
"CRITBINOM(100,-1,20%)": {"#NUM!", "#NUM!"},
"CRITBINOM(100,2,20%)": {"#NUM!", "#NUM!"},
"CRITBINOM(100,0.5,-1)": {"#NUM!", "#NUM!"},
"CRITBINOM(100,0.5,2)": {"#NUM!", "#NUM!"},
"CRITBINOM(1,1,20%)": {"#NUM!", "#NUM!"},
// DEVSQ
"DEVSQ()": {"#VALUE!", "DEVSQ requires at least 1 numeric argument"},
"DEVSQ(D1:D2)": {"#N/A", "#N/A"},
// FISHER
"FISHER()": {"#VALUE!", "FISHER requires 1 numeric argument"},
"FISHER(2)": {"#N/A", "#N/A"},
"FISHER(\"2\")": {"#N/A", "#N/A"},
"FISHER(INT(-2)))": {"#N/A", "#N/A"},
"FISHER(F1)": {"#VALUE!", "FISHER requires 1 numeric argument"},
// FISHERINV
"FISHERINV()": {"#VALUE!", "FISHERINV requires 1 numeric argument"},
"FISHERINV(F1)": {"#VALUE!", "FISHERINV requires 1 numeric argument"},
// FORECAST
"FORECAST()": {"#VALUE!", "FORECAST requires 3 arguments"},
"FORECAST(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FORECAST(1,A1:A2,B1:B1)": {"#N/A", "#N/A"},
"FORECAST(1,A4,A4)": {"#DIV/0!", "#DIV/0!"},
// FORECAST.LINEAR
"FORECAST.LINEAR()": {"#VALUE!", "FORECAST.LINEAR requires 3 arguments"},
"FORECAST.LINEAR(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FORECAST.LINEAR(1,A1:A2,B1:B1)": {"#N/A", "#N/A"},
"FORECAST.LINEAR(1,A4,A4)": {"#DIV/0!", "#DIV/0!"},
// FREQUENCY
"FREQUENCY()": {"#VALUE!", "FREQUENCY requires 2 arguments"},
"FREQUENCY(NA(),A1:A3)": {"#N/A", "#N/A"},
"FREQUENCY(A1:A3,NA())": {"#N/A", "#N/A"},
// GAMMA
"GAMMA()": {"#VALUE!", "GAMMA requires 1 numeric argument"},
"GAMMA(F1)": {"#VALUE!", "GAMMA requires 1 numeric argument"},
"GAMMA(0)": {"#N/A", "#N/A"},
"GAMMA(\"0\")": {"#N/A", "#N/A"},
"GAMMA(INT(0))": {"#N/A", "#N/A"},
// GAMMA.DIST
"GAMMA.DIST()": {"#VALUE!", "GAMMA.DIST requires 4 arguments"},
"GAMMA.DIST(\"\",3,2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.DIST(6,\"\",2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.DIST(6,3,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.DIST(6,3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"GAMMA.DIST(-1,3,2,FALSE)": {"#NUM!", "#NUM!"},
"GAMMA.DIST(6,0,2,FALSE)": {"#NUM!", "#NUM!"},
"GAMMA.DIST(6,3,0,FALSE)": {"#NUM!", "#NUM!"},
// GAMMADIST
"GAMMADIST()": {"#VALUE!", "GAMMADIST requires 4 arguments"},
"GAMMADIST(\"\",3,2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMADIST(6,\"\",2,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMADIST(6,3,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMADIST(6,3,2,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"GAMMADIST(-1,3,2,FALSE)": {"#NUM!", "#NUM!"},
"GAMMADIST(6,0,2,FALSE)": {"#NUM!", "#NUM!"},
"GAMMADIST(6,3,0,FALSE)": {"#NUM!", "#NUM!"},
// GAMMA.INV
"GAMMA.INV()": {"#VALUE!", "GAMMA.INV requires 3 arguments"},
"GAMMA.INV(\"\",3,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.INV(0.5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.INV(0.5,3,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMA.INV(-1,3,2)": {"#NUM!", "#NUM!"},
"GAMMA.INV(2,3,2)": {"#NUM!", "#NUM!"},
"GAMMA.INV(0.5,0,2)": {"#NUM!", "#NUM!"},
"GAMMA.INV(0.5,3,0)": {"#NUM!", "#NUM!"},
// GAMMAINV
"GAMMAINV()": {"#VALUE!", "GAMMAINV requires 3 arguments"},
"GAMMAINV(\"\",3,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMAINV(0.5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMAINV(0.5,3,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMAINV(-1,3,2)": {"#NUM!", "#NUM!"},
"GAMMAINV(2,3,2)": {"#NUM!", "#NUM!"},
"GAMMAINV(0.5,0,2)": {"#NUM!", "#NUM!"},
"GAMMAINV(0.5,3,0)": {"#NUM!", "#NUM!"},
// GAMMALN
"GAMMALN()": {"#VALUE!", "GAMMALN requires 1 numeric argument"},
"GAMMALN(F1)": {"#VALUE!", "GAMMALN requires 1 numeric argument"},
"GAMMALN(0)": {"#N/A", "#N/A"},
"GAMMALN(INT(0))": {"#N/A", "#N/A"},
// GAMMALN.PRECISE
"GAMMALN.PRECISE()": {"#VALUE!", "GAMMALN.PRECISE requires 1 numeric argument"},
"GAMMALN.PRECISE(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"GAMMALN.PRECISE(0)": {"#NUM!", "#NUM!"},
// GAUSS
"GAUSS()": {"#VALUE!", "GAUSS requires 1 numeric argument"},
"GAUSS(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// GEOMEAN
"GEOMEAN()": {"#VALUE!", "GEOMEAN requires at least 1 numeric argument"},
"GEOMEAN(0)": {"#NUM!", "#NUM!"},
"GEOMEAN(D1:D2)": {"#NUM!", "#NUM!"},
"GEOMEAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// HARMEAN
"HARMEAN()": {"#VALUE!", "HARMEAN requires at least 1 argument"},
"HARMEAN(-1)": {"#N/A", "#N/A"},
"HARMEAN(0)": {"#N/A", "#N/A"},
// HYPGEOM.DIST
"HYPGEOM.DIST()": {"#VALUE!", "HYPGEOM.DIST requires 5 arguments"},
"HYPGEOM.DIST(\"\",4,4,12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOM.DIST(1,\"\",4,12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOM.DIST(1,4,\"\",12,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOM.DIST(1,4,4,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOM.DIST(1,4,4,12,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"HYPGEOM.DIST(-1,4,4,12,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(2,1,4,12,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(2,4,1,12,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(2,2,2,1,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(1,0,4,12,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(1,4,4,2,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(1,4,0,12,FALSE)": {"#NUM!", "#NUM!"},
"HYPGEOM.DIST(1,4,4,0,FALSE)": {"#NUM!", "#NUM!"},
// HYPGEOMDIST
"HYPGEOMDIST()": {"#VALUE!", "HYPGEOMDIST requires 4 numeric arguments"},
"HYPGEOMDIST(\"\",4,4,12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOMDIST(1,\"\",4,12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOMDIST(1,4,\"\",12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOMDIST(1,4,4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"HYPGEOMDIST(-1,4,4,12)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(2,1,4,12)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(2,4,1,12)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(2,2,2,1)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(1,0,4,12)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(1,4,4,2)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(1,4,0,12)": {"#NUM!", "#NUM!"},
"HYPGEOMDIST(1,4,4,0)": {"#NUM!", "#NUM!"},
// INTERCEPT
"INTERCEPT()": {"#VALUE!", "INTERCEPT requires 2 arguments"},
"INTERCEPT(A1:A2,B1:B1)": {"#N/A", "#N/A"},
"INTERCEPT(A4,A4)": {"#DIV/0!", "#DIV/0!"},
// KURT
"KURT()": {"#VALUE!", "KURT requires at least 1 argument"},
"KURT(F1,INT(1))": {"#DIV/0!", "#DIV/0!"},
// EXPON.DIST
"EXPON.DIST()": {"#VALUE!", "EXPON.DIST requires 3 arguments"},
"EXPON.DIST(\"\",1,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EXPON.DIST(0,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EXPON.DIST(0,1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"EXPON.DIST(-1,1,TRUE)": {"#NUM!", "#NUM!"},
"EXPON.DIST(1,0,TRUE)": {"#NUM!", "#NUM!"},
// EXPONDIST
"EXPONDIST()": {"#VALUE!", "EXPONDIST requires 3 arguments"},
"EXPONDIST(\"\",1,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EXPONDIST(0,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EXPONDIST(0,1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"EXPONDIST(-1,1,TRUE)": {"#NUM!", "#NUM!"},
"EXPONDIST(1,0,TRUE)": {"#NUM!", "#NUM!"},
// FDIST
"FDIST()": {"#VALUE!", "FDIST requires 3 arguments"},
"FDIST(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FDIST(5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FDIST(5,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FDIST(-1,1,2)": {"#NUM!", "#NUM!"},
"FDIST(5,0,2)": {"#NUM!", "#NUM!"},
"FDIST(5,10000000000,2)": {"#NUM!", "#NUM!"},
"FDIST(5,1,0)": {"#NUM!", "#NUM!"},
"FDIST(5,1,10000000000)": {"#NUM!", "#NUM!"},
// F.DIST
"F.DIST()": {"#VALUE!", "F.DIST requires 4 arguments"},
"F.DIST(\"\",2,5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST(1,\"\",5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST(1,2,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST(1,2,5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"F.DIST(-1,1,2,TRUE)": {"#NUM!", "#NUM!"},
"F.DIST(5,0,2,TRUE)": {"#NUM!", "#NUM!"},
"F.DIST(5,10000000000,2,TRUE)": {"#NUM!", "#NUM!"},
"F.DIST(5,1,0,TRUE)": {"#NUM!", "#NUM!"},
"F.DIST(5,1,10000000000,TRUE)": {"#NUM!", "#NUM!"},
// F.DIST.RT
"F.DIST.RT()": {"#VALUE!", "F.DIST.RT requires 3 arguments"},
"F.DIST.RT(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST.RT(5,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST.RT(5,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.DIST.RT(-1,1,2)": {"#NUM!", "#NUM!"},
"F.DIST.RT(5,0,2)": {"#NUM!", "#NUM!"},
"F.DIST.RT(5,10000000000,2)": {"#NUM!", "#NUM!"},
"F.DIST.RT(5,1,0)": {"#NUM!", "#NUM!"},
"F.DIST.RT(5,1,10000000000)": {"#NUM!", "#NUM!"},
// F.INV
"F.INV()": {"#VALUE!", "F.INV requires 3 arguments"},
"F.INV(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV(0,1,2)": {"#NUM!", "#NUM!"},
"F.INV(0.2,0.5,2)": {"#NUM!", "#NUM!"},
"F.INV(0.2,1,0.5)": {"#NUM!", "#NUM!"},
// FINV
"FINV()": {"#VALUE!", "FINV requires 3 arguments"},
"FINV(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FINV(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FINV(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FINV(0,1,2)": {"#NUM!", "#NUM!"},
"FINV(0.2,0.5,2)": {"#NUM!", "#NUM!"},
"FINV(0.2,1,0.5)": {"#NUM!", "#NUM!"},
// F.INV.RT
"F.INV.RT()": {"#VALUE!", "F.INV.RT requires 3 arguments"},
"F.INV.RT(\"\",1,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV.RT(0.2,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV.RT(0.2,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"F.INV.RT(0,1,2)": {"#NUM!", "#NUM!"},
"F.INV.RT(0.2,0.5,2)": {"#NUM!", "#NUM!"},
"F.INV.RT(0.2,1,0.5)": {"#NUM!", "#NUM!"},
// LOGINV
"LOGINV()": {"#VALUE!", "LOGINV requires 3 arguments"},
"LOGINV(\"\",2,0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGINV(0.3,\"\",0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGINV(0.3,2,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGINV(0,2,0.2)": {"#NUM!", "#NUM!"},
"LOGINV(1,2,0.2)": {"#NUM!", "#NUM!"},
"LOGINV(0.3,2,0)": {"#NUM!", "#NUM!"},
// LOGNORM.INV
"LOGNORM.INV()": {"#VALUE!", "LOGNORM.INV requires 3 arguments"},
"LOGNORM.INV(\"\",2,0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.INV(0.3,\"\",0.2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.INV(0.3,2,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.INV(0,2,0.2)": {"#NUM!", "#NUM!"},
"LOGNORM.INV(1,2,0.2)": {"#NUM!", "#NUM!"},
"LOGNORM.INV(0.3,2,0)": {"#NUM!", "#NUM!"},
// LOGNORM.DIST
"LOGNORM.DIST()": {"#VALUE!", "LOGNORM.DIST requires 4 arguments"},
"LOGNORM.DIST(\"\",10,5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.DIST(0.5,\"\",5,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.DIST(0.5,10,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORM.DIST(0.5,10,5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"LOGNORM.DIST(0,10,5,FALSE)": {"#NUM!", "#NUM!"},
"LOGNORM.DIST(0.5,10,0,FALSE)": {"#NUM!", "#NUM!"},
// LOGNORMDIST
"LOGNORMDIST()": {"#VALUE!", "LOGNORMDIST requires 3 arguments"},
"LOGNORMDIST(\"\",10,5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORMDIST(12,\"\",5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORMDIST(12,10,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LOGNORMDIST(0,2,5)": {"#NUM!", "#NUM!"},
"LOGNORMDIST(12,10,0)": {"#NUM!", "#NUM!"},
// NEGBINOM.DIST
"NEGBINOM.DIST()": {"#VALUE!", "NEGBINOM.DIST requires 4 arguments"},
"NEGBINOM.DIST(\"\",12,0.5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOM.DIST(6,\"\",0.5,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOM.DIST(6,12,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOM.DIST(6,12,0.5,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"NEGBINOM.DIST(-1,12,0.5,TRUE)": {"#NUM!", "#NUM!"},
"NEGBINOM.DIST(6,0,0.5,TRUE)": {"#NUM!", "#NUM!"},
"NEGBINOM.DIST(6,12,-1,TRUE)": {"#NUM!", "#NUM!"},
"NEGBINOM.DIST(6,12,2,TRUE)": {"#NUM!", "#NUM!"},
// NEGBINOMDIST
"NEGBINOMDIST()": {"#VALUE!", "NEGBINOMDIST requires 3 arguments"},
"NEGBINOMDIST(\"\",12,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOMDIST(6,\"\",0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOMDIST(6,12,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NEGBINOMDIST(-1,12,0.5)": {"#NUM!", "#NUM!"},
"NEGBINOMDIST(6,0,0.5)": {"#NUM!", "#NUM!"},
"NEGBINOMDIST(6,12,-1)": {"#NUM!", "#NUM!"},
"NEGBINOMDIST(6,12,2)": {"#NUM!", "#NUM!"},
// NORM.DIST
"NORM.DIST()": {"#VALUE!", "NORM.DIST requires 4 arguments"},
// NORMDIST
"NORMDIST()": {"#VALUE!", "NORMDIST requires 4 arguments"},
"NORMDIST(\"\",0,0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMDIST(0,\"\",0,FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMDIST(0,0,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMDIST(0,0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"NORMDIST(0,0,-1,TRUE)": {"#N/A", "#N/A"},
// NORM.INV
"NORM.INV()": {"#VALUE!", "NORM.INV requires 3 arguments"},
// NORMINV
"NORMINV()": {"#VALUE!", "NORMINV requires 3 arguments"},
"NORMINV(\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMINV(0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMINV(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NORMINV(0,0,-1)": {"#N/A", "#N/A"},
"NORMINV(-1,0,0)": {"#N/A", "#N/A"},
"NORMINV(0,0,0)": {"#NUM!", "#NUM!"},
// NORM.S.DIST
"NORM.S.DIST()": {"#VALUE!", "NORM.S.DIST requires 2 numeric arguments"},
// NORMSDIST
"NORMSDIST()": {"#VALUE!", "NORMSDIST requires 1 numeric argument"},
// NORM.S.INV
"NORM.S.INV()": {"#VALUE!", "NORM.S.INV requires 1 numeric argument"},
// NORMSINV
"NORMSINV()": {"#VALUE!", "NORMSINV requires 1 numeric argument"},
// LARGE
"LARGE()": {"#VALUE!", "LARGE requires 2 arguments"},
"LARGE(A1:A5,0)": {"#NUM!", "k should be > 0"},
"LARGE(A1:A5,6)": {"#NUM!", "k should be <= length of array"},
"LARGE(A1:A5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// MAX
"MAX()": {"#VALUE!", "MAX requires at least 1 argument"},
"MAX(NA())": {"#N/A", "#N/A"},
// MAXA
"MAXA()": {"#VALUE!", "MAXA requires at least 1 argument"},
"MAXA(NA())": {"#N/A", "#N/A"},
// MAXIFS
"MAXIFS()": {"#VALUE!", "MAXIFS requires at least 3 arguments"},
"MAXIFS(F2:F4,A2:A4,\">0\",D2:D9)": {"#N/A", "#N/A"},
// MEDIAN
"MEDIAN()": {"#VALUE!", "MEDIAN requires at least 1 argument"},
"MEDIAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MEDIAN(D1:D2)": {"#NUM!", "#NUM!"},
// MIN
"MIN()": {"#VALUE!", "MIN requires at least 1 argument"},
"MIN(NA())": {"#N/A", "#N/A"},
// MINA
"MINA()": {"#VALUE!", "MINA requires at least 1 argument"},
"MINA(NA())": {"#N/A", "#N/A"},
// MINIFS
"MINIFS()": {"#VALUE!", "MINIFS requires at least 3 arguments"},
"MINIFS(F2:F4,A2:A4,\"<0\",D2:D9)": {"#N/A", "#N/A"},
// PEARSON
"PEARSON()": {"#VALUE!", "PEARSON requires 2 arguments"},
"PEARSON(A1:A2,B1:B1)": {"#N/A", "#N/A"},
"PEARSON(A4,A4)": {"#DIV/0!", "#DIV/0!"},
// PERCENTILE.EXC
"PERCENTILE.EXC()": {"#VALUE!", "PERCENTILE.EXC requires 2 arguments"},
"PERCENTILE.EXC(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTILE.EXC(A1:A4,-1)": {"#NUM!", "#NUM!"},
"PERCENTILE.EXC(A1:A4,0)": {"#NUM!", "#NUM!"},
"PERCENTILE.EXC(A1:A4,1)": {"#NUM!", "#NUM!"},
"PERCENTILE.EXC(NA(),0.5)": {"#NUM!", "#NUM!"},
// PERCENTILE.INC
"PERCENTILE.INC()": {"#VALUE!", "PERCENTILE.INC requires 2 arguments"},
// PERCENTILE
"PERCENTILE()": {"#VALUE!", "PERCENTILE requires 2 arguments"},
"PERCENTILE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTILE(0,-1)": {"#N/A", "#N/A"},
"PERCENTILE(NA(),1)": {"#N/A", "#N/A"},
// PERCENTRANK.EXC
"PERCENTRANK.EXC()": {"#VALUE!", "PERCENTRANK.EXC requires 2 or 3 arguments"},
"PERCENTRANK.EXC(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK.EXC(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK.EXC(A1:B4,0,0)": {"#NUM!", "PERCENTRANK.EXC arguments significance should be > 1"},
"PERCENTRANK.EXC(A1:B4,6)": {"#N/A", "#N/A"},
"PERCENTRANK.EXC(NA(),1)": {"#N/A", "#N/A"},
// PERCENTRANK.INC
"PERCENTRANK.INC()": {"#VALUE!", "PERCENTRANK.INC requires 2 or 3 arguments"},
"PERCENTRANK.INC(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK.INC(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK.INC(A1:B4,0,0)": {"#NUM!", "PERCENTRANK.INC arguments significance should be > 1"},
"PERCENTRANK.INC(A1:B4,6)": {"#N/A", "#N/A"},
"PERCENTRANK.INC(NA(),1)": {"#N/A", "#N/A"},
// PERCENTRANK
"PERCENTRANK()": {"#VALUE!", "PERCENTRANK requires 2 or 3 arguments"},
"PERCENTRANK(A1:B4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK(A1:B4,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERCENTRANK(A1:B4,0,0)": {"#NUM!", "PERCENTRANK arguments significance should be > 1"},
"PERCENTRANK(A1:B4,6)": {"#N/A", "#N/A"},
"PERCENTRANK(NA(),1)": {"#N/A", "#N/A"},
// PERMUT
"PERMUT()": {"#VALUE!", "PERMUT requires 2 numeric arguments"},
"PERMUT(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERMUT(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERMUT(6,8)": {"#N/A", "#N/A"},
// PERMUTATIONA
"PERMUTATIONA()": {"#VALUE!", "PERMUTATIONA requires 2 numeric arguments"},
"PERMUTATIONA(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERMUTATIONA(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PERMUTATIONA(-1,0)": {"#N/A", "#N/A"},
"PERMUTATIONA(0,-1)": {"#N/A", "#N/A"},
// PHI
"PHI()": {"#VALUE!", "PHI requires 1 argument"},
"PHI(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// QUARTILE
"QUARTILE()": {"#VALUE!", "QUARTILE requires 2 arguments"},
"QUARTILE(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"QUARTILE(A1:A4,-1)": {"#NUM!", "#NUM!"},
"QUARTILE(A1:A4,5)": {"#NUM!", "#NUM!"},
// QUARTILE.EXC
"QUARTILE.EXC()": {"#VALUE!", "QUARTILE.EXC requires 2 arguments"},
"QUARTILE.EXC(A1:A4,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"QUARTILE.EXC(A1:A4,0)": {"#NUM!", "#NUM!"},
"QUARTILE.EXC(A1:A4,4)": {"#NUM!", "#NUM!"},
// QUARTILE.INC
"QUARTILE.INC()": {"#VALUE!", "QUARTILE.INC requires 2 arguments"},
// RANK
"RANK()": {"#VALUE!", "RANK requires at least 2 arguments"},
"RANK(1,A1:B5,0,0)": {"#VALUE!", "RANK requires at most 3 arguments"},
"RANK(-1,A1:B5)": {"#N/A", "#N/A"},
"RANK(\"\",A1:B5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RANK(1,A1:B5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// RANK.EQ
"RANK.EQ()": {"#VALUE!", "RANK.EQ requires at least 2 arguments"},
"RANK.EQ(1,A1:B5,0,0)": {"#VALUE!", "RANK.EQ requires at most 3 arguments"},
"RANK.EQ(-1,A1:B5)": {"#N/A", "#N/A"},
"RANK.EQ(\"\",A1:B5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RANK.EQ(1,A1:B5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// RSQ
"RSQ()": {"#VALUE!", "RSQ requires 2 arguments"},
"RSQ(A1:A2,B1:B1)": {"#N/A", "#N/A"},
"RSQ(A4,A4)": {"#DIV/0!", "#DIV/0!"},
// SKEW
"SKEW()": {"#VALUE!", "SKEW requires at least 1 argument"},
"SKEW(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SKEW(0)": {"#DIV/0!", "#DIV/0!"},
// SKEW.P
"SKEW.P()": {"#VALUE!", "SKEW.P requires at least 1 argument"},
"SKEW.P(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SKEW.P(0)": {"#DIV/0!", "#DIV/0!"},
// SLOPE
"SLOPE()": {"#VALUE!", "SLOPE requires 2 arguments"},
"SLOPE(A1:A2,B1:B1)": {"#N/A", "#N/A"},
"SLOPE(A4,A4)": {"#DIV/0!", "#DIV/0!"},
// SMALL
"SMALL()": {"#VALUE!", "SMALL requires 2 arguments"},
"SMALL(A1:A5,0)": {"#NUM!", "k should be > 0"},
"SMALL(A1:A5,6)": {"#NUM!", "k should be <= length of array"},
"SMALL(A1:A5,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// STANDARDIZE
"STANDARDIZE()": {"#VALUE!", "STANDARDIZE requires 3 arguments"},
"STANDARDIZE(\"\",0,5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"STANDARDIZE(0,\"\",5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"STANDARDIZE(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"STANDARDIZE(0,0,0)": {"#N/A", "#N/A"},
// STDEVP
"STDEVP()": {"#VALUE!", "STDEVP requires at least 1 argument"},
"STDEVP(\"\")": {"#DIV/0!", "#DIV/0!"},
// STDEV.P
"STDEV.P()": {"#VALUE!", "STDEV.P requires at least 1 argument"},
"STDEV.P(\"\")": {"#DIV/0!", "#DIV/0!"},
// STDEVPA
"STDEVPA()": {"#VALUE!", "STDEVPA requires at least 1 argument"},
"STDEVPA(\"\")": {"#DIV/0!", "#DIV/0!"},
// T.DIST
"T.DIST()": {"#VALUE!", "T.DIST requires 3 arguments"},
"T.DIST(\"\",10,TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST(1,\"\",TRUE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST(1,10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"T.DIST(1,0,TRUE)": {"#NUM!", "#NUM!"},
"T.DIST(1,-1,FALSE)": {"#NUM!", "#NUM!"},
"T.DIST(1,0,FALSE)": {"#DIV/0!", "#DIV/0!"},
// T.DIST.2T
"T.DIST.2T()": {"#VALUE!", "T.DIST.2T requires 2 arguments"},
"T.DIST.2T(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST.2T(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST.2T(-1,10)": {"#NUM!", "#NUM!"},
"T.DIST.2T(1,0)": {"#NUM!", "#NUM!"},
// T.DIST.RT
"T.DIST.RT()": {"#VALUE!", "T.DIST.RT requires 2 arguments"},
"T.DIST.RT(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST.RT(1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.DIST.RT(1,0)": {"#NUM!", "#NUM!"},
// TDIST
"TDIST()": {"#VALUE!", "TDIST requires 3 arguments"},
"TDIST(\"\",10,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TDIST(1,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TDIST(1,10,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TDIST(-1,10,1)": {"#NUM!", "#NUM!"},
"TDIST(1,0,1)": {"#NUM!", "#NUM!"},
"TDIST(1,10,0)": {"#NUM!", "#NUM!"},
// T.INV
"T.INV()": {"#VALUE!", "T.INV requires 2 arguments"},
"T.INV(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.INV(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.INV(0,10)": {"#NUM!", "#NUM!"},
"T.INV(1,10)": {"#NUM!", "#NUM!"},
"T.INV(0.25,0.5)": {"#NUM!", "#NUM!"},
// T.INV.2T
"T.INV.2T()": {"#VALUE!", "T.INV.2T requires 2 arguments"},
"T.INV.2T(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.INV.2T(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"T.INV.2T(0,10)": {"#NUM!", "#NUM!"},
"T.INV.2T(0.25,0.5)": {"#NUM!", "#NUM!"},
// TINV
"TINV()": {"#VALUE!", "TINV requires 2 arguments"},
"TINV(\"\",10)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TINV(0.25,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TINV(0,10)": {"#NUM!", "#NUM!"},
"TINV(0.25,0.5)": {"#NUM!", "#NUM!"},
// TRIMMEAN
"TRIMMEAN()": {"#VALUE!", "TRIMMEAN requires 2 arguments"},
"TRIMMEAN(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TRIMMEAN(A1,1)": {"#NUM!", "#NUM!"},
"TRIMMEAN(A1,-1)": {"#NUM!", "#NUM!"},
// VAR
"VAR()": {"#VALUE!", "VAR requires at least 1 argument"},
// VARA
"VARA()": {"#VALUE!", "VARA requires at least 1 argument"},
// VARP
"VARP()": {"#VALUE!", "VARP requires at least 1 argument"},
"VARP(\"\")": {"#DIV/0!", "#DIV/0!"},
// VAR.P
"VAR.P()": {"#VALUE!", "VAR.P requires at least 1 argument"},
"VAR.P(\"\")": {"#DIV/0!", "#DIV/0!"},
// VAR.S
"VAR.S()": {"#VALUE!", "VAR.S requires at least 1 argument"},
// VARPA
"VARPA()": {"#VALUE!", "VARPA requires at least 1 argument"},
// WEIBULL
"WEIBULL()": {"#VALUE!", "WEIBULL requires 4 arguments"},
"WEIBULL(\"\",1,1,FALSE)": {"#VALUE!", "#VALUE!"},
"WEIBULL(1,0,1,FALSE)": {"#N/A", "#N/A"},
"WEIBULL(1,1,-1,FALSE)": {"#N/A", "#N/A"},
// WEIBULL.DIST
"WEIBULL.DIST()": {"#VALUE!", "WEIBULL.DIST requires 4 arguments"},
"WEIBULL.DIST(\"\",1,1,FALSE)": {"#VALUE!", "#VALUE!"},
"WEIBULL.DIST(1,0,1,FALSE)": {"#N/A", "#N/A"},
"WEIBULL.DIST(1,1,-1,FALSE)": {"#N/A", "#N/A"},
// Z.TEST
"Z.TEST(A1)": {"#VALUE!", "Z.TEST requires at least 2 arguments"},
"Z.TEST(A1,0,0,0)": {"#VALUE!", "Z.TEST accepts at most 3 arguments"},
"Z.TEST(H1,0)": {"#N/A", "#N/A"},
"Z.TEST(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"Z.TEST(A1,1)": {"#DIV/0!", "#DIV/0!"},
"Z.TEST(A1,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ZTEST
"ZTEST(A1)": {"#VALUE!", "ZTEST requires at least 2 arguments"},
"ZTEST(A1,0,0,0)": {"#VALUE!", "ZTEST accepts at most 3 arguments"},
"ZTEST(H1,0)": {"#N/A", "#N/A"},
"ZTEST(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ZTEST(A1,1)": {"#DIV/0!", "#DIV/0!"},
"ZTEST(A1,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// Information Functions
// ERROR.TYPE
"ERROR.TYPE()": {"#VALUE!", "ERROR.TYPE requires 1 argument"},
"ERROR.TYPE(1)": {"#N/A", "#N/A"},
// ISBLANK
"ISBLANK(A1,A2)": {"#VALUE!", "ISBLANK requires 1 argument"},
// ISERR
"ISERR()": {"#VALUE!", "ISERR requires 1 argument"},
// ISERROR
"ISERROR()": {"#VALUE!", "ISERROR requires 1 argument"},
// ISEVEN
"ISEVEN()": {"#VALUE!", "ISEVEN requires 1 argument"},
"ISEVEN(\"text\")": {"#VALUE!", "#VALUE!"},
"ISEVEN(A1:A2)": {"#VALUE!", "#VALUE!"},
// ISFORMULA
"ISFORMULA()": {"#VALUE!", "ISFORMULA requires 1 argument"},
// ISLOGICAL
"ISLOGICAL()": {"#VALUE!", "ISLOGICAL requires 1 argument"},
// ISNA
"ISNA()": {"#VALUE!", "ISNA requires 1 argument"},
// ISNONTEXT
"ISNONTEXT()": {"#VALUE!", "ISNONTEXT requires 1 argument"},
// ISNUMBER
"ISNUMBER()": {"#VALUE!", "ISNUMBER requires 1 argument"},
// ISODD
"ISODD()": {"#VALUE!", "ISODD requires 1 argument"},
"ISODD(\"text\")": {"#VALUE!", "#VALUE!"},
// ISREF
"ISREF()": {"#VALUE!", "ISREF requires 1 argument"},
// ISTEXT
"ISTEXT()": {"#VALUE!", "ISTEXT requires 1 argument"},
// N
"N()": {"#VALUE!", "N requires 1 argument"},
"N(NA())": {"#N/A", "#N/A"},
// NA
"NA()": {"#N/A", "#N/A"},
"NA(1)": {"#VALUE!", "NA accepts no arguments"},
// SHEET
"SHEET(\"\",\"\")": {"#VALUE!", "SHEET accepts at most 1 argument"},
"SHEET(\"Sheet2\")": {"#N/A", "#N/A"},
// SHEETS
"SHEETS(\"\",\"\")": {"#VALUE!", "SHEETS accepts at most 1 argument"},
"SHEETS(\"Sheet1\")": {"#N/A", "#N/A"},
// TYPE
"TYPE()": {"#VALUE!", "TYPE requires 1 argument"},
// T
"T()": {"#VALUE!", "T requires 1 argument"},
"T(NA())": {"#N/A", "#N/A"},
// Logical Functions
// AND
"AND(\"text\")": {"#VALUE!", "#VALUE!"},
"AND(A1:B1)": {"#VALUE!", "#VALUE!"},
"AND(\"1\",\"TRUE\",\"FALSE\")": {"#VALUE!", "#VALUE!"},
"AND()": {"#VALUE!", "AND requires at least 1 argument"},
"AND(1" + strings.Repeat(",1", 30) + ")": {"#VALUE!", "AND accepts at most 30 arguments"},
// FALSE
"FALSE(A1)": {"#VALUE!", "FALSE takes no arguments"},
// IFERROR
"IFERROR()": {"#VALUE!", "IFERROR requires 2 arguments"},
// IFNA
"IFNA()": {"#VALUE!", "IFNA requires 2 arguments"},
// IFS
"IFS()": {"#VALUE!", "IFS requires at least 2 arguments"},
"IFS(FALSE,FALSE)": {"#N/A", "#N/A"},
// NOT
"NOT()": {"#VALUE!", "NOT requires 1 argument"},
"NOT(NOT())": {"#VALUE!", "NOT requires 1 argument"},
"NOT(\"\")": {"#VALUE!", "NOT expects 1 boolean or numeric argument"},
// OR
"OR(\"text\")": {"#VALUE!", "#VALUE!"},
"OR(\"1\",\"TRUE\",\"FALSE\")": {"#VALUE!", "#VALUE!"},
"OR()": {"#VALUE!", "OR requires at least 1 argument"},
"OR(1" + strings.Repeat(",1", 30) + ")": {"#VALUE!", "OR accepts at most 30 arguments"},
// SWITCH
"SWITCH()": {"#VALUE!", "SWITCH requires at least 3 arguments"},
"SWITCH(0,1,2)": {"#N/A", "#N/A"},
// TRUE
"TRUE(A1)": {"#VALUE!", "TRUE takes no arguments"},
// XOR
"XOR()": {"#VALUE!", "XOR requires at least 1 argument"},
"XOR(\"1\")": {"#VALUE!", "#VALUE!"},
"XOR(\"text\")": {"#VALUE!", "#VALUE!"},
"XOR(XOR(\"text\"))": {"#VALUE!", "#VALUE!"},
// Date and Time Functions
// DATE
"DATE()": {"#VALUE!", "DATE requires 3 number arguments"},
"DATE(\"text\",10,21)": {"#VALUE!", "DATE requires 3 number arguments"},
"DATE(2020,\"text\",21)": {"#VALUE!", "DATE requires 3 number arguments"},
"DATE(2020,10,\"text\")": {"#VALUE!", "DATE requires 3 number arguments"},
// DATEDIF
"DATEDIF()": {"#VALUE!", "DATEDIF requires 3 number arguments"},
"DATEDIF(\"\",\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DATEDIF(43891,43101,\"Y\")": {"#NUM!", "start_date > end_date"},
"DATEDIF(43101,43891,\"x\")": {"#VALUE!", "DATEDIF has invalid unit"},
// DATEVALUE
"DATEVALUE()": {"#VALUE!", "DATEVALUE requires 1 argument"},
"DATEVALUE(\"01/01\")": {"#VALUE!", "#VALUE!"}, // valid in Excel, which uses years by the system date
"DATEVALUE(\"1900-0-0\")": {"#VALUE!", "#VALUE!"},
// DAY
"DAY()": {"#VALUE!", "DAY requires exactly 1 argument"},
"DAY(-1)": {"#NUM!", "DAY only accepts positive argument"},
"DAY(0,0)": {"#VALUE!", "DAY requires exactly 1 argument"},
"DAY(\"text\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 9223372036854775808 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 9223372036854775808:00 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 00:9223372036854775808 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 9223372036854775808:00.0 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 0:1" + strings.Repeat("0", 309) + ".0 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 9223372036854775808:00:00 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 0:9223372036854775808:0 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 0:0:1" + strings.Repeat("0", 309) + " AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 0:61:0 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 0:00:60 AM\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 24:00:00\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 2020 00:00:10001\")": {"#VALUE!", "#VALUE!"},
"DAY(\"9223372036854775808/25/2020\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01/9223372036854775808/2020\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01/25/9223372036854775808\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01/25/10000\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01/25/100\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 9223372036854775808, 2020\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 9223372036854775808\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 10000\")": {"#VALUE!", "#VALUE!"},
"DAY(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
"DAY(\"9223372036854775808-25-2020\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01-9223372036854775808-2020\")": {"#VALUE!", "#VALUE!"},
"DAY(\"01-25-9223372036854775808\")": {"#VALUE!", "#VALUE!"},
"DAY(\"1900-0-0\")": {"#VALUE!", "#VALUE!"},
"DAY(\"14-25-1900\")": {"#VALUE!", "#VALUE!"},
"DAY(\"3-January-9223372036854775808\")": {"#VALUE!", "#VALUE!"},
"DAY(\"9223372036854775808-January-1900\")": {"#VALUE!", "#VALUE!"},
"DAY(\"0-January-1900\")": {"#VALUE!", "#VALUE!"},
// DAYS
"DAYS()": {"#VALUE!", "DAYS requires 2 arguments"},
"DAYS(\"\",0)": {"#VALUE!", "#VALUE!"},
"DAYS(0,\"\")": {"#VALUE!", "#VALUE!"},
"DAYS(NA(),0)": {"#VALUE!", "#VALUE!"},
"DAYS(0,NA())": {"#VALUE!", "#VALUE!"},
// DAYS360
"DAYS360(\"12/12/1999\")": {"#VALUE!", "DAYS360 requires at least 2 arguments"},
"DAYS360(\"12/12/1999\", \"11/30/1999\",TRUE,\"\")": {"#VALUE!", "DAYS360 requires at most 3 arguments"},
"DAYS360(\"12/12/1999\", \"11/30/1999\",\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"DAYS360(\"12/12/1999\", \"\")": {"#VALUE!", "#VALUE!"},
"DAYS360(\"\", \"11/30/1999\")": {"#VALUE!", "#VALUE!"},
// EDATE
"EDATE()": {"#VALUE!", "EDATE requires 2 arguments"},
"EDATE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EDATE(-1,0)": {"#NUM!", "#NUM!"},
"EDATE(\"\",0)": {"#VALUE!", "#VALUE!"},
"EDATE(\"January 25, 100\",0)": {"#VALUE!", "#VALUE!"},
// EOMONTH
"EOMONTH()": {"#VALUE!", "EOMONTH requires 2 arguments"},
"EOMONTH(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EOMONTH(-1,0)": {"#NUM!", "#NUM!"},
"EOMONTH(\"\",0)": {"#VALUE!", "#VALUE!"},
"EOMONTH(\"January 25, 100\",0)": {"#VALUE!", "#VALUE!"},
// HOUR
"HOUR()": {"#VALUE!", "HOUR requires exactly 1 argument"},
"HOUR(-1)": {"#NUM!", "HOUR only accepts positive argument"},
"HOUR(\"\")": {"#VALUE!", "#VALUE!"},
"HOUR(\"25:10:55\")": {"#VALUE!", "#VALUE!"},
// ISOWEEKNUM
"ISOWEEKNUM()": {"#VALUE!", "ISOWEEKNUM requires 1 argument"},
"ISOWEEKNUM(\"\")": {"#VALUE!", "#VALUE!"},
"ISOWEEKNUM(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
"ISOWEEKNUM(-1)": {"#NUM!", "#NUM!"},
// MINUTE
"MINUTE()": {"#VALUE!", "MINUTE requires exactly 1 argument"},
"MINUTE(-1)": {"#NUM!", "MINUTE only accepts positive argument"},
"MINUTE(\"\")": {"#VALUE!", "#VALUE!"},
"MINUTE(\"13:60:55\")": {"#VALUE!", "#VALUE!"},
// MONTH
"MONTH()": {"#VALUE!", "MONTH requires exactly 1 argument"},
"MONTH(0,0)": {"#VALUE!", "MONTH requires exactly 1 argument"},
"MONTH(-1)": {"#NUM!", "MONTH only accepts positive argument"},
"MONTH(\"text\")": {"#VALUE!", "#VALUE!"},
"MONTH(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
// YEAR
"YEAR()": {"#VALUE!", "YEAR requires exactly 1 argument"},
"YEAR(0,0)": {"#VALUE!", "YEAR requires exactly 1 argument"},
"YEAR(-1)": {"#NUM!", "YEAR only accepts positive argument"},
"YEAR(\"text\")": {"#VALUE!", "#VALUE!"},
"YEAR(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
// YEARFRAC
"YEARFRAC()": {"#VALUE!", "YEARFRAC requires 3 or 4 arguments"},
"YEARFRAC(42005,42094,5)": {"#NUM!", "invalid basis"},
"YEARFRAC(\"\",42094,5)": {"#VALUE!", "#VALUE!"},
"YEARFRAC(42005,\"\",5)": {"#VALUE!", "#VALUE!"},
"YEARFRAC(42005,42094,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// NOW
"NOW(A1)": {"#VALUE!", "NOW accepts no arguments"},
// SECOND
"SECOND()": {"#VALUE!", "SECOND requires exactly 1 argument"},
"SECOND(-1)": {"#NUM!", "SECOND only accepts positive argument"},
"SECOND(\"\")": {"#VALUE!", "#VALUE!"},
"SECOND(\"25:55\")": {"#VALUE!", "#VALUE!"},
// TIME
"TIME()": {"#VALUE!", "TIME requires 3 number arguments"},
"TIME(\"\",0,0)": {"#VALUE!", "TIME requires 3 number arguments"},
"TIME(0,0,-1)": {"#NUM!", "#NUM!"},
// TIMEVALUE
"TIMEVALUE()": {"#VALUE!", "TIMEVALUE requires exactly 1 argument"},
"TIMEVALUE(1)": {"#VALUE!", "#VALUE!"},
"TIMEVALUE(-1)": {"#VALUE!", "#VALUE!"},
"TIMEVALUE(\"25:55\")": {"#VALUE!", "#VALUE!"},
// TODAY
"TODAY(A1)": {"#VALUE!", "TODAY accepts no arguments"},
// WEEKDAY
"WEEKDAY()": {"#VALUE!", "WEEKDAY requires at least 1 argument"},
"WEEKDAY(0,1,0)": {"#VALUE!", "WEEKDAY allows at most 2 arguments"},
"WEEKDAY(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"WEEKDAY(\"\",1)": {"#VALUE!", "#VALUE!"},
"WEEKDAY(0,0)": {"#VALUE!", "#VALUE!"},
"WEEKDAY(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
"WEEKDAY(-1,1)": {"#NUM!", "#NUM!"},
// WEEKNUM
"WEEKNUM()": {"#VALUE!", "WEEKNUM requires at least 1 argument"},
"WEEKNUM(0,1,0)": {"#VALUE!", "WEEKNUM allows at most 2 arguments"},
"WEEKNUM(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"WEEKNUM(\"\",1)": {"#VALUE!", "#VALUE!"},
"WEEKNUM(\"January 25, 100\")": {"#VALUE!", "#VALUE!"},
"WEEKNUM(0,0)": {"#NUM!", "#NUM!"},
"WEEKNUM(-1,1)": {"#NUM!", "#NUM!"},
// Text Functions
// ARRAYTOTEXT
"ARRAYTOTEXT()": {"#VALUE!", "ARRAYTOTEXT requires at least 1 argument"},
"ARRAYTOTEXT(A1,0,0)": {"#VALUE!", "ARRAYTOTEXT allows at most 2 arguments"},
"ARRAYTOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ARRAYTOTEXT(A1,2)": {"#VALUE!", "#VALUE!"},
// BAHTTEXT
"BAHTTEXT()": {"#VALUE!", "BAHTTEXT requires 1 numeric argument"},
"BAHTTEXT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// CHAR
"CHAR()": {"#VALUE!", "CHAR requires 1 argument"},
"CHAR(-1)": {"#VALUE!", "#VALUE!"},
"CHAR(256)": {"#VALUE!", "#VALUE!"},
"CHAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// CLEAN
"CLEAN()": {"#VALUE!", "CLEAN requires 1 argument"},
"CLEAN(1,2)": {"#VALUE!", "CLEAN requires 1 argument"},
// CODE
"CODE()": {"#VALUE!", "CODE requires 1 argument"},
"CODE(1,2)": {"#VALUE!", "CODE requires 1 argument"},
// CONCAT
"CONCAT(NA())": {"#N/A", "#N/A"},
"CONCAT(1,1/0)": {"#DIV/0!", "#DIV/0!"},
// CONCATENATE
"CONCATENATE(NA())": {"#N/A", "#N/A"},
"CONCATENATE(1,1/0)": {"#DIV/0!", "#DIV/0!"},
// DBCS
"DBCS(NA())": {"#N/A", "#N/A"},
"DBCS()": {"#VALUE!", "DBCS requires 1 argument"},
// EXACT
"EXACT()": {"#VALUE!", "EXACT requires 2 arguments"},
"EXACT(1,2,3)": {"#VALUE!", "EXACT requires 2 arguments"},
// FIXED
"FIXED()": {"#VALUE!", "FIXED requires at least 1 argument"},
"FIXED(0,1,2,3)": {"#VALUE!", "FIXED allows at most 3 arguments"},
"FIXED(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FIXED(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FIXED(0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
// FIND
"FIND()": {"#VALUE!", "FIND requires at least 2 arguments"},
"FIND(1,2,3,4)": {"#VALUE!", "FIND allows at most 3 arguments"},
"FIND(\"x\",\"\")": {"#VALUE!", "#VALUE!"},
"FIND(\"x\",\"x\",-1)": {"#VALUE!", "#VALUE!"},
"FIND(\"x\",\"x\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// FINDB
"FINDB()": {"#VALUE!", "FINDB requires at least 2 arguments"},
"FINDB(1,2,3,4)": {"#VALUE!", "FINDB allows at most 3 arguments"},
"FINDB(\"x\",\"\")": {"#VALUE!", "#VALUE!"},
"FINDB(\"x\",\"x\",-1)": {"#VALUE!", "#VALUE!"},
"FINDB(\"x\",\"x\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// LEFT
"LEFT()": {"#VALUE!", "LEFT requires at least 1 argument"},
"LEFT(\"\",2,3)": {"#VALUE!", "LEFT allows at most 2 arguments"},
"LEFT(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LEFT(\"\",-1)": {"#VALUE!", "#VALUE!"},
// LEFTB
"LEFTB()": {"#VALUE!", "LEFTB requires at least 1 argument"},
"LEFTB(\"\",2,3)": {"#VALUE!", "LEFTB allows at most 2 arguments"},
"LEFTB(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"LEFTB(\"\",-1)": {"#VALUE!", "#VALUE!"},
// LEN
"LEN()": {"#VALUE!", "LEN requires 1 string argument"},
// LENB
"LENB()": {"#VALUE!", "LENB requires 1 string argument"},
// LOWER
"LOWER()": {"#VALUE!", "LOWER requires 1 argument"},
"LOWER(1,2)": {"#VALUE!", "LOWER requires 1 argument"},
// MID
"MID()": {"#VALUE!", "MID requires 3 arguments"},
"MID(\"\",0,1)": {"#VALUE!", "#VALUE!"},
"MID(\"\",1,-1)": {"#VALUE!", "#VALUE!"},
"MID(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MID(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// MIDB
"MIDB()": {"#VALUE!", "MIDB requires 3 arguments"},
"MIDB(\"\",0,1)": {"#VALUE!", "#VALUE!"},
"MIDB(\"\",1,-1)": {"#VALUE!", "#VALUE!"},
"MIDB(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MIDB(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// PROPER
"PROPER()": {"#VALUE!", "PROPER requires 1 argument"},
"PROPER(1,2)": {"#VALUE!", "PROPER requires 1 argument"},
// REPLACE
"REPLACE()": {"#VALUE!", "REPLACE requires 4 arguments"},
"REPLACE(\"text\",0,4,\"string\")": {"#VALUE!", "#VALUE!"},
"REPLACE(\"text\",\"\",0,\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"REPLACE(\"text\",1,\"\",\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// REPLACEB
"REPLACEB()": {"#VALUE!", "REPLACEB requires 4 arguments"},
"REPLACEB(\"text\",0,4,\"string\")": {"#VALUE!", "#VALUE!"},
"REPLACEB(\"text\",\"\",0,\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"REPLACEB(\"text\",1,\"\",\"string\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// REPT
"REPT()": {"#VALUE!", "REPT requires 2 arguments"},
"REPT(INT(0),2)": {"#VALUE!", "REPT requires first argument to be a string"},
"REPT(\"*\",\"*\")": {"#VALUE!", "REPT requires second argument to be a number"},
"REPT(\"*\",-1)": {"#VALUE!", "REPT requires second argument to be >= 0"},
// RIGHT
"RIGHT()": {"#VALUE!", "RIGHT requires at least 1 argument"},
"RIGHT(\"\",2,3)": {"#VALUE!", "RIGHT allows at most 2 arguments"},
"RIGHT(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RIGHT(\"\",-1)": {"#VALUE!", "#VALUE!"},
// RIGHTB
"RIGHTB()": {"#VALUE!", "RIGHTB requires at least 1 argument"},
"RIGHTB(\"\",2,3)": {"#VALUE!", "RIGHTB allows at most 2 arguments"},
"RIGHTB(\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RIGHTB(\"\",-1)": {"#VALUE!", "#VALUE!"},
// SUBSTITUTE
"SUBSTITUTE()": {"#VALUE!", "SUBSTITUTE requires 3 or 4 arguments"},
"SUBSTITUTE(\"\",\"\",\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"SUBSTITUTE(\"\",\"\",\"\",0)": {"#VALUE!", "instance_num should be > 0"},
// TEXT
"TEXT()": {"#VALUE!", "TEXT requires 2 arguments"},
"TEXT(NA(),\"\")": {"#N/A", "#N/A"},
"TEXT(0,NA())": {"#N/A", "#N/A"},
// TEXTAFTER
"TEXTAFTER()": {"#VALUE!", "TEXTAFTER requires at least 2 arguments"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,0,0,\"\",0)": {"#VALUE!", "TEXTAFTER accepts at most 6 arguments"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",1,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"TEXTAFTER(\"\",\"hood\")": {"#N/A", "#N/A"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",0)": {"#VALUE!", "#VALUE!"},
"TEXTAFTER(\"Red riding hood's, red hood\",\"hood\",28)": {"#VALUE!", "#VALUE!"},
// TEXTBEFORE
"TEXTBEFORE()": {"#VALUE!", "TEXTBEFORE requires at least 2 arguments"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,0,0,\"\",0)": {"#VALUE!", "TEXTBEFORE accepts at most 6 arguments"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",1,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"TEXTBEFORE(\"\",\"hood\")": {"#N/A", "#N/A"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",0)": {"#VALUE!", "#VALUE!"},
"TEXTBEFORE(\"Red riding hood's, red hood\",\"hood\",28)": {"#VALUE!", "#VALUE!"},
// TEXTJOIN
"TEXTJOIN()": {"#VALUE!", "TEXTJOIN requires at least 3 arguments"},
"TEXTJOIN(\"\",\"\",1)": {"#VALUE!", "#VALUE!"},
"TEXTJOIN(\"\",TRUE,NA())": {"#N/A", "#N/A"},
"TEXTJOIN(\"\",TRUE," + strings.Repeat("0,", 250) + ",0)": {"#VALUE!", "TEXTJOIN accepts at most 252 arguments"},
"TEXTJOIN(\",\",FALSE,REPT(\"*\",32768))": {"#VALUE!", "TEXTJOIN function exceeds 32767 characters"},
"TEXTJOIN(\"\",FALSE,REPT(\"\U0001F600\",16384))": {"#VALUE!", "TEXTJOIN function exceeds 32767 characters"},
// TRIM
"TRIM()": {"#VALUE!", "TRIM requires 1 argument"},
"TRIM(1,2)": {"#VALUE!", "TRIM requires 1 argument"},
// UNICHAR
"UNICHAR()": {"#VALUE!", "UNICHAR requires 1 argument"},
"UNICHAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"UNICHAR(55296)": {"#VALUE!", "#VALUE!"},
"UNICHAR(0)": {"#VALUE!", "#VALUE!"},
// UNICODE
"UNICODE()": {"#VALUE!", "UNICODE requires 1 argument"},
"UNICODE(\"\")": {"#VALUE!", "#VALUE!"},
// VALUE
"VALUE()": {"#VALUE!", "VALUE requires 1 argument"},
"VALUE(\"\")": {"#VALUE!", "#VALUE!"},
// VALUETOTEXT
"VALUETOTEXT()": {"#VALUE!", "VALUETOTEXT requires at least 1 argument"},
"VALUETOTEXT(A1,0,0)": {"#VALUE!", "VALUETOTEXT allows at most 2 arguments"},
"VALUETOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VALUETOTEXT(A1,2)": {"#VALUE!", "#VALUE!"},
// UPPER
"UPPER()": {"#VALUE!", "UPPER requires 1 argument"},
"UPPER(1,2)": {"#VALUE!", "UPPER requires 1 argument"},
// Conditional Functions
// IF
"IF()": {"#VALUE!", "IF requires at least 1 argument"},
"IF(0,1,2,3)": {"#VALUE!", "IF accepts at most 3 arguments"},
"IF(D1,1,2)": {"#VALUE!", "strconv.ParseBool: parsing \"Month\": invalid syntax"},
// Excel Lookup and Reference Functions
// ADDRESS
"ADDRESS()": {"#VALUE!", "ADDRESS requires at least 2 arguments"},
"ADDRESS(1,1,1,TRUE,\"Sheet1\",0)": {"#VALUE!", "ADDRESS requires at most 5 arguments"},
"ADDRESS(\"\",1,1,TRUE)": {"#VALUE!", "#VALUE!"},
"ADDRESS(1,\"\",1,TRUE)": {"#VALUE!", "#VALUE!"},
"ADDRESS(1,1,\"\",TRUE)": {"#VALUE!", "#VALUE!"},
"ADDRESS(1,1,1,\"\")": {"#VALUE!", "#VALUE!"},
"ADDRESS(1,1,0,TRUE)": {"#NUM!", "#NUM!"},
"ADDRESS(1,16385,2,TRUE)": {"#VALUE!", "#VALUE!"},
"ADDRESS(1,16385,3,TRUE)": {"#VALUE!", "#VALUE!"},
"ADDRESS(1048577,1,1,TRUE)": {"#VALUE!", "#VALUE!"},
// CHOOSE
"CHOOSE()": {"#VALUE!", "CHOOSE requires 2 arguments"},
"CHOOSE(\"index_num\",0)": {"#VALUE!", "CHOOSE requires first argument of type number"},
"CHOOSE(2,0)": {"#VALUE!", "index_num should be <= to the number of values"},
"CHOOSE(1,NA())": {"#N/A", "#N/A"},
// COLUMN
"COLUMN(1,2)": {"#VALUE!", "COLUMN requires at most 1 argument"},
"COLUMN(\"\")": {"#VALUE!", "invalid reference"},
"COLUMN(Sheet1)": {"#NAME?", "invalid reference"},
"COLUMN(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"COLUMN(Sheet1!A1:Sheet2!A2)": {"#NAME?", "invalid reference"},
"COLUMN(Sheet1!A1:1A)": {"#NAME?", "invalid reference"},
// COLUMNS
"COLUMNS()": {"#VALUE!", "COLUMNS requires 1 argument"},
"COLUMNS(1)": {"#VALUE!", "invalid reference"},
"COLUMNS(\"\")": {"#VALUE!", "invalid reference"},
"COLUMNS(Sheet1)": {"#NAME?", "invalid reference"},
"COLUMNS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"COLUMNS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"},
// FORMULATEXT
"FORMULATEXT()": {"#VALUE!", "FORMULATEXT requires 1 argument"},
"FORMULATEXT(1)": {"#VALUE!", "#VALUE!"},
// HLOOKUP
"HLOOKUP()": {"#VALUE!", "HLOOKUP requires at least 3 arguments"},
"HLOOKUP(D2,D1,1,FALSE)": {"#VALUE!", "HLOOKUP requires second argument of table array"},
"HLOOKUP(D2,D:D,FALSE,FALSE)": {"#VALUE!", "HLOOKUP requires numeric row argument"},
"HLOOKUP(D2,D:D,1,FALSE,FALSE)": {"#VALUE!", "HLOOKUP requires at most 4 arguments"},
"HLOOKUP(D2,D:D,1,2)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(D2,D10:D10,1,FALSE)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(D2,D2:D3,4,FALSE)": {"#N/A", "HLOOKUP has invalid row index"},
"HLOOKUP(D2,C:C,1,FALSE)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(ISNUMBER(1),F3:F9,1)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(INT(1),E2:E9,1)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(MUNIT(2),MUNIT(3),1)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(A1:B2,B2:B3,1)": {"#N/A", "HLOOKUP no result found"},
// MATCH
"MATCH()": {"#VALUE!", "MATCH requires 1 or 2 arguments"},
"MATCH(0,A1:A1,0,0)": {"#VALUE!", "MATCH requires 1 or 2 arguments"},
"MATCH(0,A1:A1,\"x\")": {"#VALUE!", "MATCH requires numeric match_type argument"},
"MATCH(0,A1)": {"#N/A", "MATCH arguments lookup_array should be one-dimensional array"},
"MATCH(0,A1:B2)": {"#N/A", "MATCH arguments lookup_array should be one-dimensional array"},
"MATCH(0,A1:B1)": {"#N/A", "#N/A"},
// TRANSPOSE
"TRANSPOSE()": {"#VALUE!", "TRANSPOSE requires 1 argument"},
// HYPERLINK
"HYPERLINK()": {"#VALUE!", "HYPERLINK requires at least 1 argument"},
"HYPERLINK(\"https://github.com/xuri/excelize\",\"Excelize\",\"\")": {"#VALUE!", "HYPERLINK allows at most 2 arguments"},
// VLOOKUP
"VLOOKUP()": {"#VALUE!", "VLOOKUP requires at least 3 arguments"},
"VLOOKUP(D2,D1,1,FALSE)": {"#VALUE!", "VLOOKUP requires second argument of table array"},
"VLOOKUP(D2,D:D,FALSE,FALSE)": {"#VALUE!", "VLOOKUP requires numeric col argument"},
"VLOOKUP(D2,D:D,1,FALSE,FALSE)": {"#VALUE!", "VLOOKUP requires at most 4 arguments"},
"VLOOKUP(D2,D10:D10,1,FALSE)": {"#N/A", "VLOOKUP no result found"},
"VLOOKUP(D2,D:D,2,FALSE)": {"#N/A", "VLOOKUP has invalid column index"},
"VLOOKUP(D2,C:C,1,FALSE)": {"#N/A", "VLOOKUP no result found"},
"VLOOKUP(ISNUMBER(1),F3:F9,1)": {"#N/A", "VLOOKUP no result found"},
"VLOOKUP(INT(1),E2:E9,1)": {"#N/A", "VLOOKUP no result found"},
"VLOOKUP(MUNIT(2),MUNIT(3),1)": {"#N/A", "VLOOKUP no result found"},
"VLOOKUP(1,G1:H2,1,FALSE)": {"#N/A", "VLOOKUP no result found"},
// INDEX
"INDEX()": {"#VALUE!", "INDEX requires 2 or 3 arguments"},
"INDEX(A1,2)": {"#REF!", "INDEX row_num out of range"},
"INDEX(A1,0,2)": {"#REF!", "INDEX col_num out of range"},
"INDEX(A1:A1,2)": {"#REF!", "INDEX row_num out of range"},
"INDEX(A1:A1,0,2)": {"#REF!", "INDEX col_num out of range"},
"INDEX(A1:B2,2,3)": {"#REF!", "INDEX col_num out of range"},
"INDEX(A1:A2,0,0)": {"#VALUE!", "#VALUE!"},
"INDEX(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"INDEX(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// INDIRECT
"INDIRECT()": {"#VALUE!", "INDIRECT requires 1 or 2 arguments"},
"INDIRECT(\"E\"&1,TRUE,1)": {"#VALUE!", "INDIRECT requires 1 or 2 arguments"},
"INDIRECT(\"R1048577C1\",\"\")": {"#VALUE!", "#VALUE!"},
"INDIRECT(\"E1048577\")": {"#REF!", "#REF!"},
"INDIRECT(\"R1048577C1\",FALSE)": {"#REF!", "#REF!"},
"INDIRECT(\"R1C16385\",FALSE)": {"#REF!", "#REF!"},
"INDIRECT(\"\",FALSE)": {"#REF!", "#REF!"},
"INDIRECT(\"R C1\",FALSE)": {"#REF!", "#REF!"},
"INDIRECT(\"R1C \",FALSE)": {"#REF!", "#REF!"},
"INDIRECT(\"R1C1:R2C \",FALSE)": {"#REF!", "#REF!"},
// LOOKUP
"LOOKUP()": {"#VALUE!", "LOOKUP requires at least 2 arguments"},
"LOOKUP(D2,D1,D2)": {"#VALUE!", "LOOKUP requires second argument of table array"},
"LOOKUP(D2,D1,D2,FALSE)": {"#VALUE!", "LOOKUP requires at most 3 arguments"},
"LOOKUP(1,MUNIT(0))": {"#VALUE!", "LOOKUP requires not empty range as second argument"},
"LOOKUP(D1,MUNIT(1),MUNIT(1))": {"#N/A", "LOOKUP no result found"},
// ROW
"ROW(1,2)": {"#VALUE!", "ROW requires at most 1 argument"},
"ROW(\"\")": {"#VALUE!", "invalid reference"},
"ROW(Sheet1)": {"#NAME?", "invalid reference"},
"ROW(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
// ROWS
"ROWS()": {"#VALUE!", "ROWS requires 1 argument"},
"ROWS(1)": {"#VALUE!", "invalid reference"},
"ROWS(\"\")": {"#VALUE!", "invalid reference"},
"ROWS(Sheet1)": {"#NAME?", "invalid reference"},
"ROWS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"ROWS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"},
// Web Functions
// ENCODEURL
"ENCODEURL()": {"#VALUE!", "ENCODEURL requires 1 argument"},
// Financial Functions
// ACCRINT
"ACCRINT()": {"#VALUE!", "ACCRINT requires at least 6 arguments"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,FALSE,0)": {"#VALUE!", "ACCRINT allows at most 8 arguments"},
"ACCRINT(\"\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"},
"ACCRINT(\"01/01/2012\",\"\",\"12/31/2013\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"\",8%,10000,4,1,FALSE)": {"#VALUE!", "#VALUE!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",\"\",10000,4,1,FALSE)": {"#NUM!", "#NUM!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,\"\",4,1,FALSE)": {"#NUM!", "#NUM!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,3)": {"#NUM!", "#NUM!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,\"\",1,FALSE)": {"#NUM!", "#NUM!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,\"\",FALSE)": {"#NUM!", "#NUM!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,1,\"\")": {"#VALUE!", "#VALUE!"},
"ACCRINT(\"01/01/2012\",\"04/01/2012\",\"12/31/2013\",8%,10000,4,5,FALSE)": {"#NUM!", "invalid basis"},
// ACCRINTM
"ACCRINTM()": {"#VALUE!", "ACCRINTM requires 4 or 5 arguments"},
"ACCRINTM(\"\",\"01/01/2012\",8%,10000)": {"#VALUE!", "#VALUE!"},
"ACCRINTM(\"01/01/2012\",\"\",8%,10000)": {"#VALUE!", "#VALUE!"},
"ACCRINTM(\"12/31/2012\",\"01/01/2012\",8%,10000)": {"#NUM!", "#NUM!"},
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",\"\",10000)": {"#NUM!", "#NUM!"},
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,\"\",10000)": {"#NUM!", "#NUM!"},
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,-1,10000)": {"#NUM!", "#NUM!"},
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,\"\")": {"#NUM!", "#NUM!"},
"ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,5)": {"#NUM!", "invalid basis"},
// AMORDEGRC
"AMORDEGRC()": {"#VALUE!", "AMORDEGRC requires 6 or 7 arguments"},
"AMORDEGRC(\"\",\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORDEGRC requires cost to be number argument"},
"AMORDEGRC(-1,\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORDEGRC requires cost >= 0"},
"AMORDEGRC(150,\"\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "#VALUE!"},
"AMORDEGRC(150,\"01/01/2015\",\"\",20,1,20%)": {"#VALUE!", "#VALUE!"},
"AMORDEGRC(150,\"09/30/2015\",\"01/01/2015\",20,1,20%)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",\"\",1,20%)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",-1,1,20%)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,\"\",20%)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,-1,20%)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,\"\")": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,-1)": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,\"\")": {"#NUM!", "#NUM!"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,50%)": {"#NUM!", "AMORDEGRC requires rate to be < 0.5"},
"AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,5)": {"#NUM!", "invalid basis"},
// AMORLINC
"AMORLINC()": {"#VALUE!", "AMORLINC requires 6 or 7 arguments"},
"AMORLINC(\"\",\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORLINC requires cost to be number argument"},
"AMORLINC(-1,\"01/01/2015\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "AMORLINC requires cost >= 0"},
"AMORLINC(150,\"\",\"09/30/2015\",20,1,20%)": {"#VALUE!", "#VALUE!"},
"AMORLINC(150,\"01/01/2015\",\"\",20,1,20%)": {"#VALUE!", "#VALUE!"},
"AMORLINC(150,\"09/30/2015\",\"01/01/2015\",20,1,20%)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",\"\",1,20%)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",-1,1,20%)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,\"\",20%)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,-1,20%)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,\"\")": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,-1)": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,\"\")": {"#NUM!", "#NUM!"},
"AMORLINC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,5)": {"#NUM!", "invalid basis"},
// COUPDAYBS
"COUPDAYBS()": {"#VALUE!", "COUPDAYBS requires 3 or 4 arguments"},
"COUPDAYBS(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYBS(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYBS(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPDAYBS(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPDAYBS(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYBS requires maturity > settlement"},
// COUPDAYS
"COUPDAYS()": {"#VALUE!", "COUPDAYS requires 3 or 4 arguments"},
"COUPDAYS(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYS(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYS(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPDAYS(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYS requires maturity > settlement"},
// COUPDAYSNC
"COUPDAYSNC()": {"#VALUE!", "COUPDAYSNC requires 3 or 4 arguments"},
"COUPDAYSNC(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYSNC(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPDAYSNC(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPDAYSNC requires maturity > settlement"},
// COUPNCD
"COUPNCD()": {"#VALUE!", "COUPNCD requires 3 or 4 arguments"},
"COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPNCD requires 3 or 4 arguments"},
"COUPNCD(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPNCD(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPNCD(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPNCD(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPNCD(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"},
"COUPNCD(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPNCD requires maturity > settlement"},
// COUPNUM
"COUPNUM()": {"#VALUE!", "COUPNUM requires 3 or 4 arguments"},
"COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPNUM requires 3 or 4 arguments"},
"COUPNUM(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPNUM(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPNUM(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPNUM(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPNUM(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"},
"COUPNUM(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPNUM requires maturity > settlement"},
// COUPPCD
"COUPPCD()": {"#VALUE!", "COUPPCD requires 3 or 4 arguments"},
"COUPPCD(\"01/01/2011\",\"10/25/2012\",4,0,0)": {"#VALUE!", "COUPPCD requires 3 or 4 arguments"},
"COUPPCD(\"\",\"10/25/2012\",4)": {"#VALUE!", "#VALUE!"},
"COUPPCD(\"01/01/2011\",\"\",4)": {"#VALUE!", "#VALUE!"},
"COUPPCD(\"01/01/2011\",\"10/25/2012\",\"\")": {"#VALUE!", "#VALUE!"},
"COUPPCD(\"01/01/2011\",\"10/25/2012\",4,\"\")": {"#NUM!", "#NUM!"},
"COUPPCD(\"01/01/2011\",\"10/25/2012\",3)": {"#NUM!", "#NUM!"},
"COUPPCD(\"10/25/2012\",\"01/01/2011\",4)": {"#NUM!", "COUPPCD requires maturity > settlement"},
// CUMIPMT
"CUMIPMT()": {"#VALUE!", "CUMIPMT requires 6 arguments"},
"CUMIPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"},
"CUMIPMT(0,0,0,-1,0,0)": {"#N/A", "#N/A"},
"CUMIPMT(0,0,0,1,0,0)": {"#N/A", "#N/A"},
"CUMIPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMIPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMIPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMIPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMIPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMIPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// CUMPRINC
"CUMPRINC()": {"#VALUE!", "CUMPRINC requires 6 arguments"},
"CUMPRINC(0,0,0,0,0,2)": {"#N/A", "#N/A"},
"CUMPRINC(0,0,0,-1,0,0)": {"#N/A", "#N/A"},
"CUMPRINC(0,0,0,1,0,0)": {"#N/A", "#N/A"},
"CUMPRINC(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMPRINC(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMPRINC(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMPRINC(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMPRINC(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"CUMPRINC(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// DB
"DB()": {"#VALUE!", "DB requires at least 4 arguments"},
"DB(0,0,0,0,0,0)": {"#VALUE!", "DB allows at most 5 arguments"},
"DB(-1,0,0,0)": {"#N/A", "#N/A"},
"DB(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DB(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DB(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DB(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DB(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// DDB
"DDB()": {"#VALUE!", "DDB requires at least 4 arguments"},
"DDB(0,0,0,0,0,0)": {"#VALUE!", "DDB allows at most 5 arguments"},
"DDB(-1,0,0,0)": {"#N/A", "#N/A"},
"DDB(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DDB(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DDB(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DDB(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DDB(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// DISC
"DISC()": {"#VALUE!", "DISC requires 4 or 5 arguments"},
"DISC(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"},
"DISC(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"},
"DISC(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"},
"DISC(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"},
"DISC(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"},
"DISC(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "DISC requires maturity > settlement"},
"DISC(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "DISC requires pr > 0"},
"DISC(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "DISC requires redemption > 0"},
"DISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"},
// DOLLAR
"DOLLAR()": {"#VALUE!", "DOLLAR requires at least 1 argument"},
"DOLLAR(0,0,0)": {"#VALUE!", "DOLLAR requires 1 or 2 arguments"},
"DOLLAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLAR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLAR(1,200)": {"#VALUE!", "decimal value should be less than 128"},
// DOLLARDE
"DOLLARDE()": {"#VALUE!", "DOLLARDE requires 2 arguments"},
"DOLLARDE(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLARDE(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLARDE(0,-1)": {"#NUM!", "#NUM!"},
"DOLLARDE(0,0)": {"#DIV/0!", "#DIV/0!"},
// DOLLARFR
"DOLLARFR()": {"#VALUE!", "DOLLARFR requires 2 arguments"},
"DOLLARFR(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLARFR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DOLLARFR(0,-1)": {"#NUM!", "#NUM!"},
"DOLLARFR(0,0)": {"#DIV/0!", "#DIV/0!"},
// DURATION
"DURATION()": {"#VALUE!", "DURATION requires 5 or 6 arguments"},
"DURATION(\"\",\"03/31/2025\",10%,8%,4)": {"#VALUE!", "#VALUE!"},
"DURATION(\"04/01/2015\",\"\",10%,8%,4)": {"#VALUE!", "#VALUE!"},
"DURATION(\"03/31/2025\",\"04/01/2015\",10%,8%,4)": {"#NUM!", "DURATION requires maturity > settlement"},
"DURATION(\"04/01/2015\",\"03/31/2025\",-1,8%,4)": {"#NUM!", "DURATION requires coupon >= 0"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,-1,4)": {"#NUM!", "DURATION requires yld >= 0"},
"DURATION(\"04/01/2015\",\"03/31/2025\",\"\",8%,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,3)": {"#NUM!", "#NUM!"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,\"\")": {"#NUM!", "#NUM!"},
"DURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,5)": {"#NUM!", "invalid basis"},
// EFFECT
"EFFECT()": {"#VALUE!", "EFFECT requires 2 arguments"},
"EFFECT(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EFFECT(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EFFECT(0,0)": {"#NUM!", "#NUM!"},
"EFFECT(1,0)": {"#NUM!", "#NUM!"},
// EUROCONVERT
"EUROCONVERT()": {"#VALUE!", "EUROCONVERT requires at least 3 arguments"},
"EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,3,1)": {"#VALUE!", "EUROCONVERT allows at most 5 arguments"},
"EUROCONVERT(\"\",\"FRF\",\"DEM\",TRUE,3)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EUROCONVERT(1.47,\"FRF\",\"DEM\",\"\",3)": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"EUROCONVERT(1.47,\"FRF\",\"DEM\",TRUE,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"EUROCONVERT(1.47,\"\",\"DEM\")": {"#VALUE!", "#VALUE!"},
"EUROCONVERT(1.47,\"FRF\",\"\",TRUE,3)": {"#VALUE!", "#VALUE!"},
// FV
"FV()": {"#VALUE!", "FV requires at least 3 arguments"},
"FV(0,0,0,0,0,0,0)": {"#VALUE!", "FV allows at most 5 arguments"},
"FV(0,0,0,0,2)": {"#N/A", "#N/A"},
"FV(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FV(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FV(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FV(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FV(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// FVSCHEDULE
"FVSCHEDULE()": {"#VALUE!", "FVSCHEDULE requires 2 arguments"},
"FVSCHEDULE(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"FVSCHEDULE(0,\"x\")": {"#VALUE!", "strconv.ParseFloat: parsing \"x\": invalid syntax"},
// INTRATE
"INTRATE()": {"#VALUE!", "INTRATE requires 4 or 5 arguments"},
"INTRATE(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"},
"INTRATE(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"},
"INTRATE(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "INTRATE requires maturity > settlement"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "INTRATE requires investment > 0"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "INTRATE requires redemption > 0"},
"INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"},
// IPMT
"IPMT()": {"#VALUE!", "IPMT requires at least 4 arguments"},
"IPMT(0,0,0,0,0,0,0)": {"#VALUE!", "IPMT allows at most 6 arguments"},
"IPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"},
"IPMT(0,-1,0,0,0,0)": {"#N/A", "#N/A"},
"IPMT(0,1,0,0,0,0)": {"#N/A", "#N/A"},
"IPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ISPMT
"ISPMT()": {"#VALUE!", "ISPMT requires 4 arguments"},
"ISPMT(\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ISPMT(0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ISPMT(0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ISPMT(0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// MDURATION
"MDURATION()": {"#VALUE!", "MDURATION requires 5 or 6 arguments"},
"MDURATION(\"\",\"03/31/2025\",10%,8%,4)": {"#VALUE!", "#VALUE!"},
"MDURATION(\"04/01/2015\",\"\",10%,8%,4)": {"#VALUE!", "#VALUE!"},
"MDURATION(\"03/31/2025\",\"04/01/2015\",10%,8%,4)": {"#NUM!", "MDURATION requires maturity > settlement"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",-1,8%,4)": {"#NUM!", "MDURATION requires coupon >= 0"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,-1,4)": {"#NUM!", "MDURATION requires yld >= 0"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",\"\",8%,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,3)": {"#NUM!", "#NUM!"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,\"\")": {"#NUM!", "#NUM!"},
"MDURATION(\"04/01/2015\",\"03/31/2025\",10%,8%,4,5)": {"#NUM!", "invalid basis"},
// NOMINAL
"NOMINAL()": {"#VALUE!", "NOMINAL requires 2 arguments"},
"NOMINAL(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NOMINAL(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NOMINAL(0,0)": {"#NUM!", "#NUM!"},
"NOMINAL(1,0)": {"#NUM!", "#NUM!"},
// NPER
"NPER()": {"#VALUE!", "NPER requires at least 3 arguments"},
"NPER(0,0,0,0,0,0)": {"#VALUE!", "NPER allows at most 5 arguments"},
"NPER(0,0,0)": {"#NUM!", "#NUM!"},
"NPER(0,0,0,0,2)": {"#N/A", "#N/A"},
"NPER(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NPER(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NPER(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NPER(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"NPER(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// NPV
"NPV()": {"#VALUE!", "NPV requires at least 2 arguments"},
"NPV(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ODDFPRICE
"ODDFPRICE()": {"#VALUE!", "ODDFPRICE requires 8 or 9 arguments"},
"ODDFPRICE(\"\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFPRICE(\"02/01/2017\",\"\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"02/01/2017\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires settlement > issue"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"02/01/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires first_coupon > settlement"},
"ODDFPRICE(\"02/01/2017\",\"02/01/2017\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires maturity > first_coupon"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",-1,3.5%,100,2)": {"#NUM!", "ODDFPRICE requires rate >= 0"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,-1,100,2)": {"#NUM!", "ODDFPRICE requires yld >= 0"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,0,2)": {"#NUM!", "ODDFPRICE requires redemption > 0"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"},
"ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
// ODDFYIELD
"ODDFYIELD()": {"#VALUE!", "ODDFYIELD requires 8 or 9 arguments"},
"ODDFYIELD(\"\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFYIELD(\"02/01/2017\",\"\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"02/01/2017\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires settlement > issue"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"02/01/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires first_coupon > settlement"},
"ODDFYIELD(\"02/01/2017\",\"02/01/2017\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires maturity > first_coupon"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",-1,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires rate >= 0"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,0,100,2)": {"#NUM!", "ODDFYIELD requires pr > 0"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,0,2)": {"#NUM!", "ODDFYIELD requires redemption > 0"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"},
"ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
// ODDLPRICE
"ODDLPRICE()": {"#VALUE!", "ODDLPRICE requires 7 or 8 arguments"},
"ODDLPRICE(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLPRICE(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires settlement > last_interest"},
"ODDLPRICE(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires maturity > settlement"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLPRICE requires rate >= 0"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,-1,100,2)": {"#NUM!", "ODDLPRICE requires yld >= 0"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLPRICE requires redemption > 0"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
"ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
// ODDLYIELD
"ODDLYIELD()": {"#VALUE!", "ODDLYIELD requires 7 or 8 arguments"},
"ODDLYIELD(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLYIELD(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires settlement > last_interest"},
"ODDLYIELD(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires maturity > settlement"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLYIELD requires rate >= 0"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,0,100,2)": {"#NUM!", "ODDLYIELD requires pr > 0"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLYIELD requires redemption > 0"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
"ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
// PDURATION
"PDURATION()": {"#VALUE!", "PDURATION requires 3 arguments"},
"PDURATION(\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PDURATION(0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PDURATION(0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PDURATION(0,0,0)": {"#NUM!", "#NUM!"},
// PMT
"PMT()": {"#VALUE!", "PMT requires at least 3 arguments"},
"PMT(0,0,0,0,0,0)": {"#VALUE!", "PMT allows at most 5 arguments"},
"PMT(0,0,0,0,2)": {"#N/A", "#N/A"},
"PMT(\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PMT(0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PMT(0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PMT(0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PMT(0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// PRICE
"PRICE()": {"#VALUE!", "PRICE requires 6 or 7 arguments"},
"PRICE(\"\",\"02/01/2020\",12%,10%,100,2,4)": {"#VALUE!", "#VALUE!"},
"PRICE(\"04/01/2012\",\"\",12%,10%,100,2,4)": {"#VALUE!", "#VALUE!"},
"PRICE(\"04/01/2012\",\"02/01/2020\",\"\",10%,100,2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,\"\",100,2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,\"\",2,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICE(\"04/01/2012\",\"02/01/2020\",-1,10%,100,2,4)": {"#NUM!", "PRICE requires rate >= 0"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,-1,100,2,4)": {"#NUM!", "PRICE requires yld >= 0"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,0,2,4)": {"#NUM!", "PRICE requires redemption > 0"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,\"\")": {"#NUM!", "#NUM!"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,3,4)": {"#NUM!", "#NUM!"},
"PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,5)": {"#NUM!", "invalid basis"},
// PPMT
"PPMT()": {"#VALUE!", "PPMT requires at least 4 arguments"},
"PPMT(0,0,0,0,0,0,0)": {"#VALUE!", "PPMT allows at most 6 arguments"},
"PPMT(0,0,0,0,0,2)": {"#N/A", "#N/A"},
"PPMT(0,-1,0,0,0,0)": {"#N/A", "#N/A"},
"PPMT(0,1,0,0,0,0)": {"#N/A", "#N/A"},
"PPMT(\"\",0,0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PPMT(0,\"\",0,0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PPMT(0,0,\"\",0,0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PPMT(0,0,0,\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PPMT(0,0,0,0,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PPMT(0,0,0,0,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// PRICEDISC
"PRICEDISC()": {"#VALUE!", "PRICEDISC requires 4 or 5 arguments"},
"PRICEDISC(\"\",\"03/31/2021\",95,100)": {"#VALUE!", "#VALUE!"},
"PRICEDISC(\"04/01/2016\",\"\",95,100)": {"#VALUE!", "#VALUE!"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",\"\",100)": {"#VALUE!", "#VALUE!"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,\"\")": {"#VALUE!", "#VALUE!"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": {"#NUM!", "#NUM!"},
"PRICEDISC(\"03/31/2021\",\"04/01/2016\",95,100)": {"#NUM!", "PRICEDISC requires maturity > settlement"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "PRICEDISC requires discount > 0"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "PRICEDISC requires redemption > 0"},
"PRICEDISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"},
// PRICEMAT
"PRICEMAT()": {"#VALUE!", "PRICEMAT requires 5 or 6 arguments"},
"PRICEMAT(\"\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"},
"PRICEMAT(\"04/01/2017\",\"\",\"01/01/2017\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"\",4.5%,2.5%)": {"#VALUE!", "#VALUE!"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,\"\")": {"#NUM!", "#NUM!"},
"PRICEMAT(\"03/31/2021\",\"04/01/2017\",\"01/01/2017\",4.5%,2.5%)": {"#NUM!", "PRICEMAT requires maturity > settlement"},
"PRICEMAT(\"01/01/2017\",\"03/31/2021\",\"04/01/2017\",4.5%,2.5%)": {"#NUM!", "PRICEMAT requires settlement > issue"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",-1,2.5%)": {"#NUM!", "PRICEMAT requires rate >= 0"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,-1)": {"#NUM!", "PRICEMAT requires yld >= 0"},
"PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,5)": {"#NUM!", "invalid basis"},
// PV
"PV()": {"#VALUE!", "PV requires at least 3 arguments"},
"PV(10%/4,16,2000,0,1,0)": {"#VALUE!", "PV allows at most 5 arguments"},
"PV(\"\",16,2000,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PV(10%/4,\"\",2000,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PV(10%/4,16,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PV(10%/4,16,2000,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"PV(10%/4,16,2000,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// RATE
"RATE()": {"#VALUE!", "RATE requires at least 3 arguments"},
"RATE(48,-200,8000,3,1,0.5,0)": {"#VALUE!", "RATE allows at most 6 arguments"},
"RATE(\"\",-200,8000,3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RATE(48,\"\",8000,3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RATE(48,-200,\"\",3,1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RATE(48,-200,8000,\"\",1,0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RATE(48,-200,8000,3,\"\",0.5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RATE(48,-200,8000,3,1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// RECEIVED
"RECEIVED()": {"#VALUE!", "RECEIVED requires at least 4 arguments"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,1,0)": {"#VALUE!", "RECEIVED allows at most 5 arguments"},
"RECEIVED(\"\",\"03/31/2016\",1000,4.5%,1)": {"#VALUE!", "#VALUE!"},
"RECEIVED(\"04/01/2011\",\"\",1000,4.5%,1)": {"#VALUE!", "#VALUE!"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",\"\",4.5%,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,\"\")": {"#NUM!", "#NUM!"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,0)": {"#NUM!", "RECEIVED requires discount > 0"},
"RECEIVED(\"04/01/2011\",\"03/31/2016\",1000,4.5%,5)": {"#NUM!", "invalid basis"},
// RRI
"RRI()": {"#VALUE!", "RRI requires 3 arguments"},
"RRI(\"\",\"\",\"\")": {"#NUM!", "#NUM!"},
"RRI(0,10000,15000)": {"#NUM!", "RRI requires nper argument to be > 0"},
"RRI(10,0,15000)": {"#NUM!", "RRI requires pv argument to be > 0"},
"RRI(10,10000,-1)": {"#NUM!", "RRI requires fv argument to be >= 0"},
// SLN
"SLN()": {"#VALUE!", "SLN requires 3 arguments"},
"SLN(\"\",\"\",\"\")": {"#NUM!", "#NUM!"},
"SLN(10000,1000,0)": {"#NUM!", "SLN requires life argument to be > 0"},
// SYD
"SYD()": {"#VALUE!", "SYD requires 4 arguments"},
"SYD(\"\",\"\",\"\",\"\")": {"#NUM!", "#NUM!"},
"SYD(10000,1000,0,1)": {"#NUM!", "SYD requires life argument to be > 0"},
"SYD(10000,1000,5,0)": {"#NUM!", "SYD requires per argument to be > 0"},
"SYD(10000,1000,1,5)": {"#NUM!", "#NUM!"},
// TBILLEQ
"TBILLEQ()": {"#VALUE!", "TBILLEQ requires 3 arguments"},
"TBILLEQ(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLEQ(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLEQ(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"},
"TBILLEQ(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"},
"TBILLEQ(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"},
"TBILLEQ(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"},
// TBILLPRICE
"TBILLPRICE()": {"#VALUE!", "TBILLPRICE requires 3 arguments"},
"TBILLPRICE(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLPRICE(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLPRICE(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"},
"TBILLPRICE(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"},
"TBILLPRICE(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"},
"TBILLPRICE(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"},
// TBILLYIELD
"TBILLYIELD()": {"#VALUE!", "TBILLYIELD requires 3 arguments"},
"TBILLYIELD(\"\",\"06/30/2017\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLYIELD(\"01/01/2017\",\"\",2.5%)": {"#VALUE!", "#VALUE!"},
"TBILLYIELD(\"01/01/2017\",\"06/30/2017\",\"\")": {"#VALUE!", "#VALUE!"},
"TBILLYIELD(\"01/01/2017\",\"06/30/2017\",0)": {"#NUM!", "#NUM!"},
"TBILLYIELD(\"01/01/2017\",\"06/30/2018\",2.5%)": {"#NUM!", "#NUM!"},
"TBILLYIELD(\"06/30/2017\",\"01/01/2017\",2.5%)": {"#NUM!", "#NUM!"},
// VDB
"VDB()": {"#VALUE!", "VDB requires 5 or 7 arguments"},
"VDB(-1,1000,5,0,1)": {"#NUM!", "VDB requires cost >= 0"},
"VDB(10000,-1,5,0,1)": {"#NUM!", "VDB requires salvage >= 0"},
"VDB(10000,1000,0,0,1)": {"#NUM!", "VDB requires life > 0"},
"VDB(10000,1000,5,-1,1)": {"#NUM!", "VDB requires start_period > 0"},
"VDB(10000,1000,5,2,1)": {"#NUM!", "VDB requires start_period <= end_period"},
"VDB(10000,1000,5,0,6)": {"#NUM!", "VDB requires end_period <= life"},
"VDB(10000,1000,5,0,1,-0.2)": {"#VALUE!", "VDB requires factor >= 0"},
"VDB(\"\",1000,5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VDB(10000,\"\",5,0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VDB(10000,1000,\"\",0,1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VDB(10000,1000,5,\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VDB(10000,1000,5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"VDB(10000,1000,5,0,1,\"\")": {"#NUM!", "#NUM!"},
"VDB(10000,1000,5,0,1,0.2,\"\")": {"#NUM!", "#NUM!"},
// YIELD
"YIELD()": {"#VALUE!", "YIELD requires 6 or 7 arguments"},
"YIELD(\"\",\"06/30/2015\",10%,101,100,4)": {"#VALUE!", "#VALUE!"},
"YIELD(\"01/01/2010\",\"\",10%,101,100,4)": {"#VALUE!", "#VALUE!"},
"YIELD(\"01/01/2010\",\"06/30/2015\",\"\",101,100,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,\"\",100,4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,\"\",4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,\"\")": {"#NUM!", "#NUM!"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,3)": {"#NUM!", "#NUM!"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,5)": {"#NUM!", "invalid basis"},
"YIELD(\"01/01/2010\",\"06/30/2015\",-1,101,100,4)": {"#NUM!", "YIELD requires rate >= 0"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,0,100,4)": {"#NUM!", "YIELD requires pr > 0"},
"YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,-1,4)": {"#NUM!", "YIELD requires redemption >= 0"},
// YIELDDISC
"YIELDDISC()": {"#VALUE!", "YIELDDISC requires 4 or 5 arguments"},
"YIELDDISC(\"\",\"06/30/2017\",97,100,0)": {"#VALUE!", "#VALUE!"},
"YIELDDISC(\"01/01/2017\",\"\",97,100,0)": {"#VALUE!", "#VALUE!"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",\"\",100,0)": {"#VALUE!", "#VALUE!"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,\"\",0)": {"#VALUE!", "#VALUE!"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,\"\")": {"#NUM!", "#NUM!"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",0,100)": {"#NUM!", "YIELDDISC requires pr > 0"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,0)": {"#NUM!", "YIELDDISC requires redemption > 0"},
"YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,5)": {"#NUM!", "invalid basis"},
// YIELDMAT
"YIELDMAT()": {"#VALUE!", "YIELDMAT requires 5 or 6 arguments"},
"YIELDMAT(\"\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": {"#VALUE!", "#VALUE!"},
"YIELDMAT(\"01/01/2017\",\"\",\"06/01/2014\",5.5%,101,0)": {"#VALUE!", "#VALUE!"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"\",5.5%,101,0)": {"#VALUE!", "#VALUE!"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",\"\",101,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,5.5%,\"\")": {"#NUM!", "#NUM!"},
"YIELDMAT(\"06/01/2014\",\"06/30/2018\",\"01/01/2017\",5.5%,101,0)": {"#NUM!", "YIELDMAT requires settlement > issue"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",-1,101,0)": {"#NUM!", "YIELDMAT requires rate >= 0"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",1,0,0)": {"#NUM!", "YIELDMAT requires pr > 0"},
"YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,5)": {"#NUM!", "invalid basis"},
// DISPIMG
"_xlfn.DISPIMG()": {"#VALUE!", "DISPIMG requires 2 numeric arguments"},
}
for formula, expected := range mathCalcError {
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
referenceCalc := map[string]string{
// MDETERM
"MDETERM(A1:B2)": "-3",
// PRODUCT
"PRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4",
// IMPRODUCT
"IMPRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4",
// SUM
"A1/A3": "0.333333333333333",
"SUM(A1:A2)": "3",
"SUM(Sheet1!A1:Sheet1!A2)": "3",
"SUM(Sheet1!A1,A2)": "3",
"(-2-SUM(-4+A2))*5": "0",
"SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5",
"SUM(A1,A2,A3)*SUM(2,3)": "30",
"1+SUM(SUM(A1+A2/A3)*(2-3),2)": "1.33333333333333",
"A1/A2/SUM(A1:A2:B1)": "0.0416666666666667",
"A1/A2/SUM(A1:A2:B1)*A3": "0.125",
"SUM(B1:D1)": "4",
"SUM(\"X\")": "0",
}
for formula, expected := range referenceCalc {
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, expected, result, formula)
}
referenceCalcError := map[string][]string{
// MDETERM
"MDETERM(A1:B3)": {"#VALUE!", "#VALUE!"},
// SUM
"1+SUM(SUM(A1+A2/A4)*(2-3),2)": {"#VALUE!", "#DIV/0!"},
}
for formula, expected := range referenceCalcError {
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
volatileFuncs := []string{
"NOW()",
"RAND()",
"RANDBETWEEN(1,2)",
"TODAY()",
}
for _, formula := range volatileFuncs {
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
_, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
}
// Test get calculated cell value on not formula cell
f := prepareCalcData(cellData)
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "1", result)
// Test get calculated cell value on not exists worksheet
f = prepareCalcData(cellData)
_, err = f.CalcCellValue("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get calculated cell value with invalid sheet name
_, err = f.CalcCellValue("Sheet:1", "A1")
assert.Equal(t, ErrSheetNameInvalid, err)
// Test get calculated cell value with not support formula
f = prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "UNSUPPORT(A1)"))
_, err = f.CalcCellValue("Sheet1", "A1")
assert.EqualError(t, err, "not support UNSUPPORT function")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx")))
}
func TestCalcWithDefinedName(t *testing.T) {
cellData := [][]interface{}{
{"A1_as_string", "B1_as_string", 123, nil},
}
f := prepareCalcData(cellData)
assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!A1", Scope: "Workbook"}))
assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!B1", Scope: "Sheet1"}))
assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name2", RefersTo: "Sheet1!C1", Scope: "Workbook"}))
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "defined_name1"))
result, err := f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
// DefinedName with scope WorkSheet takes precedence over DefinedName with scope Workbook, so we should get B1 value
assert.Equal(t, "B1_as_string", result, "defined_name1")
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "CONCATENATE(\"<\",defined_name1,\">\")"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "", result, "defined_name1")
// comparing numeric values
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "123=defined_name2"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "TRUE", result, "123=defined_name2")
// comparing text values
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "\"B1_as_string\"=defined_name1"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "TRUE", result, "\"B1_as_string\"=defined_name1")
// comparing text values
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "YES", result, "IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")")
t.Run("for_sheet_name_with_space", func(t *testing.T) {
f := NewFile()
defer func() {
assert.NoError(t, f.Close())
}()
assert.NoError(t, f.SetSheetName("Sheet1", "Sheet 1"))
cells := []string{"A1", "A2", "A3", "A4"}
names := []string{"val1", "val2", "val3", "val4"}
for idx, v := range []interface{}{100, 20, 30, 5} {
assert.NoError(t, f.SetCellValue("Sheet 1", cells[idx], v))
}
for idx, cell := range cells {
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: names[idx], RefersTo: "'Sheet 1'!" + cell,
}))
}
assert.NoError(t, f.SetCellFormula("Sheet 1", "B1", "=val1-val2-val3-val4"))
result, err := f.CalcCellValue("Sheet 1", "B1")
assert.NoError(t, err)
assert.Equal(t, "45", result)
})
}
func TestCalcISBLANK(t *testing.T) {
argsList := list.New()
argsList.PushBack(formulaArg{
Type: ArgUnknown,
})
fn := formulaFuncs{}
result := fn.ISBLANK(argsList)
assert.Equal(t, "TRUE", result.Value())
assert.Empty(t, result.Error)
}
func TestCalcAND(t *testing.T) {
argsList := list.New()
argsList.PushBack(formulaArg{
Type: ArgUnknown,
})
fn := formulaFuncs{}
result := fn.AND(argsList)
assert.Equal(t, result.String, "")
assert.Empty(t, result.Error)
}
func TestCalcOR(t *testing.T) {
argsList := list.New()
argsList.PushBack(formulaArg{
Type: ArgUnknown,
})
fn := formulaFuncs{}
result := fn.OR(argsList)
assert.Equal(t, result.Value(), "FALSE")
assert.Empty(t, result.Error)
}
func TestCalcDet(t *testing.T) {
assert.Equal(t, det([][]float64{
{1, 2, 3, 4},
{2, 3, 4, 5},
{3, 4, 5, 6},
{4, 5, 6, 7},
}), float64(0))
}
func TestCalcToBool(t *testing.T) {
b := newBoolFormulaArg(true).ToBool()
assert.Equal(t, b.Boolean, true)
assert.Equal(t, b.Number, 1.0)
}
func TestCalcToList(t *testing.T) {
assert.Equal(t, []formulaArg(nil), newEmptyFormulaArg().ToList())
formulaList := []formulaArg{newEmptyFormulaArg()}
assert.Equal(t, formulaList, newListFormulaArg(formulaList).ToList())
}
func TestCalcCompareFormulaArg(t *testing.T) {
assert.Equal(t, compareFormulaArg(newEmptyFormulaArg(), newEmptyFormulaArg(), newNumberFormulaArg(matchModeMaxLess), false), criteriaEq)
lhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg()})
rhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg(), newEmptyFormulaArg()})
assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaL)
assert.Equal(t, compareFormulaArg(rhs, lhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG)
lhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)})
rhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)})
assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaEq)
lhs = newListFormulaArg([]formulaArg{newNumberFormulaArg(1)})
rhs = newListFormulaArg([]formulaArg{newNumberFormulaArg(0)})
assert.Equal(t, compareFormulaArg(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG)
assert.Equal(t, compareFormulaArg(formulaArg{Type: ArgUnknown}, formulaArg{Type: ArgUnknown}, newNumberFormulaArg(matchModeMaxLess), false), criteriaErr)
}
func TestCalcCompareFormulaArgMatrix(t *testing.T) {
lhs := newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg()}})
rhs := newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg(), newEmptyFormulaArg()}})
assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaL)
lhs = newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg(), newEmptyFormulaArg()}})
rhs = newMatrixFormulaArg([][]formulaArg{{newEmptyFormulaArg()}})
assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG)
lhs = newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}})
rhs = newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(0)}})
assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG)
}
func TestCalcANCHORARRAY(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 2))
formulaType, ref := STCellFormulaTypeArray, "B1:B2"
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A1:A2",
FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY($B$1))"))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "3", result)
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY(\"\",\"\"))"))
result, err = f.CalcCellValue("Sheet1", "C1")
assert.EqualError(t, err, "ANCHORARRAY requires 1 numeric argument")
assert.Equal(t, "#VALUE!", result)
fn := &formulaFuncs{f: f, sheet: "SheetN"}
argsList := list.New()
argsList.PushBack(newStringFormulaArg("$B$1"))
formulaArg := fn.ANCHORARRAY(argsList)
assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value())
fn.sheet = "Sheet1"
argsList = argsList.Init()
arg := newStringFormulaArg("$A$1")
arg.cellRefs = list.New()
arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1})
argsList.PushBack(arg)
formulaArg = fn.ANCHORARRAY(argsList)
assert.Equal(t, ArgEmpty, formulaArg.Type)
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{}
formulaArg = fn.ANCHORARRAY(argsList)
assert.Equal(t, ArgError, formulaArg.Type)
assert.Equal(t, ErrParameterInvalid.Error(), formulaArg.Value())
argsList = argsList.Init()
arg = newStringFormulaArg("$B$1")
arg.cellRefs = list.New()
arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1, Sheet: "SheetN"})
argsList.PushBack(arg)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{Ref: "A1:A1"}
formulaArg = fn.ANCHORARRAY(argsList)
assert.Equal(t, ArgError, formulaArg.Type)
assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value())
}
func TestCalcArrayFormula(t *testing.T) {
t.Run("matrix_multiplication", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
formulaType, ref := STCellFormulaTypeArray, "C1:C2"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2",
FormulaOpts{Ref: &ref, Type: &formulaType}))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "2", result)
result, err = f.CalcCellValue("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "12", result)
})
t.Run("matrix_multiplication_with_defined_name", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "matrix",
RefersTo: "Sheet1!$A$1:$A$2",
}))
formulaType, ref := STCellFormulaTypeArray, "C1:C2"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "matrix*B1:B2+\"1\"",
FormulaOpts{Ref: &ref, Type: &formulaType}))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "3", result)
result, err = f.CalcCellValue("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "13", result)
})
t.Run("columm_multiplication", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
formulaType, ref := STCellFormulaTypeArray, "C1:C1048576"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A:A*B:B",
FormulaOpts{Ref: &ref, Type: &formulaType}))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "2", result)
result, err = f.CalcCellValue("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "12", result)
})
t.Run("row_multiplication", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
formulaType, ref := STCellFormulaTypeArray, "A3:XFD3"
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "1:1*2:2",
FormulaOpts{Ref: &ref, Type: &formulaType}))
result, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, "3", result)
result, err = f.CalcCellValue("Sheet1", "B3")
assert.NoError(t, err)
assert.Equal(t, "8", result)
})
}
func TestCalcTRANSPOSE(t *testing.T) {
cellData := [][]interface{}{
{"a", "d"},
{"b", "e"},
{"c", "f"},
}
formula := "=TRANSPOSE(A1:A3)"
f := prepareCalcData(cellData)
formulaType, ref := STCellFormulaTypeArray, "D1:F2"
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula,
FormulaOpts{Ref: &ref, Type: &formulaType}))
_, err := f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err, formula)
}
func TestCalcVLOOKUP(t *testing.T) {
cellData := [][]interface{}{
{nil, nil, nil, nil, nil, nil},
{nil, "Score", "Grade", nil, nil, nil},
{nil, 0, "F", nil, "Score", 85},
{nil, 60, "D", nil, "Grade"},
{nil, 70, "C", nil, nil, nil},
{nil, 80, "b", nil, nil, nil},
{nil, 90, "A", nil, nil, nil},
{nil, 85, "B", nil, nil, nil},
{nil, nil, nil, nil, nil, nil},
}
f := prepareCalcData(cellData)
calc := map[string]string{
"VLOOKUP(F3,B3:C8,2)": "b",
"VLOOKUP(F3,B3:C8,2,TRUE)": "b",
"VLOOKUP(F3,B3:C8,2,FALSE)": "B",
}
for formula, expected := range calc {
assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula))
result, err := f.CalcCellValue("Sheet1", "F4")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"VLOOKUP(INT(1),C3:C3,1,FALSE)": {"#N/A", "VLOOKUP no result found"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula))
result, err := f.CalcCellValue("Sheet1", "F4")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
argsList := list.New()
argsList.PushBack(newStringFormulaArg(""))
argsList.PushBack(newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}}))
argsList.PushBack(newNumberFormulaArg(1))
argsList.PushBack(newStringFormulaArg(""))
_, _, _, _, err := checkHVLookupArgs("VLOOKUP", argsList)
assert.Equal(t, ArgError, err.Type)
}
func TestCalcBoolean(t *testing.T) {
cellData := [][]interface{}{{0.5, "TRUE", -0.5, "FALSE", true}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"AVERAGEA(A1:C1)": "0.333333333333333",
"MAX(0.5,B1)": "0.5",
"MAX(A1:B1)": "0.5",
"MAXA(A1:B1)": "0.5",
"MAXA(A1:E1)": "1",
"MAXA(0.5,B1)": "1",
"MIN(-0.5,D1)": "-0.5",
"MIN(C1:D1)": "-0.5",
"MINA(C1:D1)": "-0.5",
"MINA(-0.5,D1)": "-0.5",
"STDEV(A1:C1)": "0.707106781186548",
"STDEV(A1,B1,C1)": "0.707106781186548",
"STDEVA(A1:C1,B1)": "0.707106781186548",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
result, err := f.CalcCellValue("Sheet1", "B10")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcMAXMIN(t *testing.T) {
cellData := [][]interface{}{{"1"}, {"2"}, {true}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"MAX(A1:A3)": "0",
"MAXA(A1:A3)": "1",
"MIN(A1:A3)": "0",
"MINA(A1:A3)": "1",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcAVERAGEIF(t *testing.T) {
f := prepareCalcData([][]interface{}{
{"Monday", 500},
{"Tuesday", 50},
{"Thursday", 100},
{"Friday", 100},
{"Thursday", 200},
{5, 300},
{2, 200},
{3, 100},
{4, 50},
{5, 100},
{1, 50},
{true, 200},
{true, 250},
{false, 50},
})
for formula, expected := range map[string]string{
"AVERAGEIF(A1:A14,\"Thursday\",B1:B14)": "150",
"AVERAGEIF(A1:A14,5,B1:B14)": "200",
"AVERAGEIF(A1:A14,\">2\",B1:B14)": "137.5",
"AVERAGEIF(A1:A14,TRUE,B1:B14)": "225",
"AVERAGEIF(A1:A14,\"<>TRUE\",B1:B14)": "150",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcCOVAR(t *testing.T) {
cellData := [][]interface{}{
{"array1", "array2"},
{2, 22.9},
{7, 33.49},
{8, 34.5},
{3, 27.61},
{4, 19.5},
{1, 10.11},
{6, 37.9},
{5, 31.08},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"COVAR(A1:A9,B1:B9)": "16.633125",
"COVAR(A2:A9,B2:B9)": "16.633125",
"COVARIANCE.P(A1:A9,B1:B9)": "16.633125",
"COVARIANCE.P(A2:A9,B2:B9)": "16.633125",
"COVARIANCE.S(A1:A9,B1:B9)": "19.0092857142857",
"COVARIANCE.S(A2:A9,B2:B9)": "19.0092857142857",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"COVAR()": {"#VALUE!", "COVAR requires 2 arguments"},
"COVAR(A2:A9,B3:B3)": {"#N/A", "#N/A"},
"COVARIANCE.P()": {"#VALUE!", "COVARIANCE.P requires 2 arguments"},
"COVARIANCE.P(A2:A9,B3:B3)": {"#N/A", "#N/A"},
"COVARIANCE.S()": {"#VALUE!", "COVARIANCE.S requires 2 arguments"},
"COVARIANCE.S(A2:A9,B3:B3)": {"#N/A", "#N/A"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcUniqueExactlyOnce(t *testing.T) {
cellData := [][]interface{}{
{"Customer name"},
{"Fife, Grant"},
{"Pruitt, Barbara"},
{"Horn, Frances"},
{"Barrett, Alicia"},
{"Barrett, Alicia"},
{"Larson, Lynn"},
{"Pruitt, Barbara"},
{"Snook, Anthony"},
{"Snook, Anthony"},
{"Horn, Frances"},
{"Brown, Charity"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,TRUE))": "Fife, Grant:Larson, Lynn:Brown, Charity",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,FALSE))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcUniqueMultiColumn(t *testing.T) {
cellData := [][]interface{}{
{"Player name", "Gender", "Nickname"},
{"Tom", "M", "Tom"},
{"Fred", "M", "Fred"},
{"Amy", "F", "Amy"},
{"John", "M", "John"},
{"Malicia", "F", "Malicia"},
{"Fred", "M", "Fred"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7))": "Tom:M:Tom:Fred:M:Fred:Amy:F:Amy:John:M:John:Malicia:F:Malicia",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE))": "Tom:M:Fred:M:Amy:F:John:M:Malicia:F:Fred:M",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE, TRUE))": "M:M:F:M:F:M",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcUniqueErrors(t *testing.T) {
cellData := [][]interface{}{
{"Player name", "Gender", "Nickname"},
{"Tom", "M", "Tom"},
{"Fred", "M", "Fred"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"TEXTJOIN(\":\", TRUE, UNIQUE())": "#VALUE!",
"TEXTJOIN(\":\", TRUE, UNIQUE(1, 2, 3, 4))": "#VALUE!",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, \"Hello\"))": "#VALUE!",
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, TRUE, \"Hello\"))": "#VALUE!",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Error(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestTransposeFormulaArgsMatrix(t *testing.T) {
assert.Empty(t, transposeFormulaArgsMatrix([][]formulaArg{}))
}
func TestGetFormulaUniqueArgs(t *testing.T) {
argsList := list.New()
emptyArg := newEmptyFormulaArg()
argsList.PushBack(emptyArg)
_, err := getFormulaUniqueArgs(argsList)
assert.Equal(t, "missing first argument to UNIQUE", err.Error)
argsList = list.New()
argsList.PushBack(newListFormulaArg([]formulaArg{newErrorFormulaArg(formulaErrorNAME, formulaErrorNAME)}))
_, err = getFormulaUniqueArgs(argsList)
assert.Equal(t, formulaErrorNAME, err.Error)
}
func TestCalcDatabase(t *testing.T) {
cellData := [][]interface{}{
{"Tree", "Height", "Age", "Yield", "Profit", "Height"},
{nil, ">1000%", nil, nil, nil, "<16"},
{},
{"Tree", "Height", "Age", "Yield", "Profit"},
{"Apple", 18, 20, 14, 105},
{"Pear", 12, 12, 10, 96},
{"Cherry", 13, 14, 9, 105},
{"Apple", 14, nil, 10, 75},
{"Pear", 9, 8, 8, 77},
{"Apple", 12, 11, 6, 45},
}
f := prepareCalcData(cellData)
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "\"=Apple\""))
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "\"=Pear\""))
assert.NoError(t, f.SetCellFormula("Sheet1", "C8", "NA()"))
formulaList := map[string]string{
"DAVERAGE(A4:E10,\"Profit\",A1:F3)": "73.25",
"DCOUNT(A4:E10,\"Age\",A1:F2)": "1",
"DCOUNT(A4:E10,,A1:F2)": "2",
"DCOUNT(A4:E10,\"Profit\",A1:F2)": "2",
"DCOUNT(A4:E10,\"Tree\",A1:F2)": "0",
"DCOUNT(A4:E10,\"Age\",A2:F3)": "0",
"DCOUNTA(A4:E10,\"Age\",A1:F2)": "1",
"DCOUNTA(A4:E10,,A1:F2)": "2",
"DCOUNTA(A4:E10,\"Profit\",A1:F2)": "2",
"DCOUNTA(A4:E10,\"Tree\",A1:F2)": "2",
"DCOUNTA(A4:E10,\"Age\",A2:F3)": "0",
"DGET(A4:E6,\"Profit\",A1:F3)": "96",
"DMAX(A4:E10,\"Tree\",A1:F3)": "0",
"DMAX(A4:E10,\"Profit\",A1:F3)": "96",
"DMIN(A4:E10,\"Tree\",A1:F3)": "0",
"DMIN(A4:E10,\"Profit\",A1:F3)": "45",
"DPRODUCT(A4:E10,\"Profit\",A1:F3)": "24948000",
"DSTDEV(A4:E10,\"Profit\",A1:F3)": "21.077238908358",
"DSTDEVP(A4:E10,\"Profit\",A1:F3)": "18.2534243362718",
"DSUM(A4:E10,\"Profit\",A1:F3)": "293",
"DVAR(A4:E10,\"Profit\",A1:F3)": "444.25",
"DVARP(A4:E10,\"Profit\",A1:F3)": "333.1875",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula))
result, err := f.CalcCellValue("Sheet1", "A11")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"DAVERAGE()": {"#VALUE!", "DAVERAGE requires 3 arguments"},
"DAVERAGE(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DAVERAGE(A4:E10,\"Tree\",A1:F3)": {"#DIV/0!", "#DIV/0!"},
"DCOUNT()": {"#VALUE!", "DCOUNT requires at least 2 arguments"},
"DCOUNT(A4:E10,\"Age\",A1:F2,\"\")": {"#VALUE!", "DCOUNT allows at most 3 arguments"},
"DCOUNT(A4,\"Age\",A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNT(A4:E10,NA(),A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNT(A4:E4,,A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNT(A4:E10,\"x\",A2:F3)": {"#VALUE!", "#VALUE!"},
"DCOUNTA()": {"#VALUE!", "DCOUNTA requires at least 2 arguments"},
"DCOUNTA(A4:E10,\"Age\",A1:F2,\"\")": {"#VALUE!", "DCOUNTA allows at most 3 arguments"},
"DCOUNTA(A4,\"Age\",A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNTA(A4:E10,NA(),A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNTA(A4:E4,,A1:F2)": {"#VALUE!", "#VALUE!"},
"DCOUNTA(A4:E10,\"x\",A2:F3)": {"#VALUE!", "#VALUE!"},
"DGET()": {"#VALUE!", "DGET requires 3 arguments"},
"DGET(A1,\"Profit\",A1)": {"#VALUE!", "#VALUE!"},
"DGET(A4:E5,\"Profit\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DGET(A4:E10,\"Profit\",A1:F3)": {"#NUM!", "#NUM!"},
"DMAX()": {"#VALUE!", "DMAX requires 3 arguments"},
"DMAX(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DMIN()": {"#VALUE!", "DMIN requires 3 arguments"},
"DMIN(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DPRODUCT()": {"#VALUE!", "DPRODUCT requires 3 arguments"},
"DPRODUCT(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DSTDEV()": {"#VALUE!", "DSTDEV requires 3 arguments"},
"DSTDEV(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DSTDEVP()": {"#VALUE!", "DSTDEVP requires 3 arguments"},
"DSTDEVP(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DSUM()": {"#VALUE!", "DSUM requires 3 arguments"},
"DSUM(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DVAR()": {"#VALUE!", "DVAR requires 3 arguments"},
"DVAR(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
"DVARP()": {"#VALUE!", "DVARP requires 3 arguments"},
"DVARP(A4:E10,\"x\",A1:F3)": {"#VALUE!", "#VALUE!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula))
result, err := f.CalcCellValue("Sheet1", "A11")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcDBCS(t *testing.T) {
f := NewFile(Options{CultureInfo: CultureNameZhCN})
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "DBCS(\"`~·!@#$¥%…^&*()_-+=[]{}\\|;:'\"\"<,>.?/01234567890 abc ABC \uff65\uff9e\uff9f \uff74\uff78\uff7e\uff99\")"))
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "\uff40\uff5e\u00b7\uff01\uff20\uff03\uff04\u00a5\uff05\u2026\uff3e\uff06\uff0a\uff08\uff09\uff3f\uff0d\uff0b\uff1d\uff3b\uff3d\uff5b\uff5d\uff3c\uff5c\uff1b\uff1a\uff07\uff02\uff1c\uff0c\uff1e\uff0e\uff1f\uff0f\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19\uff10\u3000\uff41\uff42\uff43\u3000\uff21\uff22\uff23\u3000\uff65\uff9e\uff9f\u3000\uff74\uff78\uff7e\uff99", result)
}
func TestCalcFORMULATEXT(t *testing.T) {
f, formulaText := NewFile(), "=SUM(B1:C1)"
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formulaText))
for _, formula := range []string{"FORMULATEXT(A1)", "FORMULATEXT(A:A)", "FORMULATEXT(A1:B1)"} {
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula), formula)
result, err := f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err, formula)
assert.Equal(t, formulaText, result, formula)
}
}
func TestCalcGROWTHandTREND(t *testing.T) {
cellData := [][]interface{}{
{"known_x's", "known_y's", 0, -1},
{1, 10, 1},
{2, 20, 1},
{3, 40},
{4, 80},
{},
{"new_x's", "new_y's"},
{5},
{6},
{7},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"GROWTH(A2:B2)": "1",
"GROWTH(B2:B5,A2:A5,A8:A10)": "160",
"GROWTH(B2:B5,A2:A5,A8:A10,FALSE)": "467.842838114059",
"GROWTH(A4:A5,A2:B3,A8:A10,FALSE)": "",
"GROWTH(A3:A5,A2:B4,A2:B3)": "2",
"GROWTH(A4:A5,A2:B3)": "",
"GROWTH(A2:B2,A2:B3)": "",
"GROWTH(A2:B2,A2:B3,A2:B3,FALSE)": "1.28402541668774",
"GROWTH(A2:B2,A4:B5,A4:B5,FALSE)": "1",
"GROWTH(A3:C3,A2:C3,A2:B3)": "2",
"TREND(A2:B2)": "1",
"TREND(B2:B5,A2:A5,A8:A10)": "95",
"TREND(B2:B5,A2:A5,A8:A10,FALSE)": "81.6666666666667",
"TREND(A4:A5,A2:B3,A8:A10,FALSE)": "",
"TREND(A4:A5,A2:B3,A2:B3,FALSE)": "1.5",
"TREND(A3:A5,A2:B4,A2:B3)": "2",
"TREND(A4:A5,A2:B3)": "",
"TREND(A2:B2,A2:B3)": "",
"TREND(A2:B2,A2:B3,A2:B3,FALSE)": "1",
"TREND(A2:B2,A4:B5,A4:B5,FALSE)": "1",
"TREND(A3:C3,A2:C3,A2:B3)": "2",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"GROWTH()": {"#VALUE!", "GROWTH requires at least 1 argument"},
"GROWTH(B2:B5,A2:A5,A8:A10,TRUE,0)": {"#VALUE!", "GROWTH allows at most 4 arguments"},
"GROWTH(A1:B1,A2:A5,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"},
"GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"},
"GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": {"#VALUE!", "#VALUE!"},
"GROWTH(B2:B5,A2:A5,A8:A10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"GROWTH(A2:B3,A4:B4)": {"#REF!", "#REF!"},
"GROWTH(A4:B4,A2:A2)": {"#REF!", "#REF!"},
"GROWTH(A2:A2,A4:A5)": {"#REF!", "#REF!"},
"GROWTH(C1:C1,A2:A3)": {"#VALUE!", "#VALUE!"},
"GROWTH(D1:D1,A2:A3)": {"#NUM!", "#NUM!"},
"GROWTH(A2:A3,C1:C1)": {"#VALUE!", "#VALUE!"},
"TREND()": {"#VALUE!", "TREND requires at least 1 argument"},
"TREND(B2:B5,A2:A5,A8:A10,TRUE,0)": {"#VALUE!", "TREND allows at most 4 arguments"},
"TREND(A1:B1,A2:A5,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"},
"TREND(B2:B5,A1:B1,A8:A10,TRUE)": {"#VALUE!", "#VALUE!"},
"TREND(B2:B5,A2:A5,A1:B1,TRUE)": {"#VALUE!", "#VALUE!"},
"TREND(B2:B5,A2:A5,A8:A10,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
"TREND(A2:B3,A4:B4)": {"#REF!", "#REF!"},
"TREND(A4:B4,A2:A2)": {"#REF!", "#REF!"},
"TREND(A2:A2,A4:A5)": {"#REF!", "#REF!"},
"TREND(C1:C1,A2:A3)": {"#VALUE!", "#VALUE!"},
"TREND(D1:D1,A2:A3)": {"#REF!", "#REF!"},
"TREND(A2:A3,C1:C1)": {"#VALUE!", "#VALUE!"},
"TREND(C1:C1,C1:C1)": {"#VALUE!", "#VALUE!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcHLOOKUP(t *testing.T) {
cellData := [][]interface{}{
{"Example Result Table"},
{nil, "A", "B", "C", "E", "F"},
{"Math", .58, .9, .67, .76, .8},
{"French", .61, .71, .59, .59, .76},
{"Physics", .75, .45, .39, .52, .69},
{"Biology", .39, .55, .77, .61, .45},
{},
{"Individual Student Score"},
{"Student:", "Biology Score:"},
{"E"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"HLOOKUP(A10,A2:F6,5,FALSE)": "0.61",
"HLOOKUP(D3,D3:D3,1,TRUE)": "0.67",
"HLOOKUP(F3,D3:F3,1,TRUE)": "0.8",
"HLOOKUP(A5,A2:F2,1,TRUE)": "F",
"HLOOKUP(\"D\",A2:F2,1,TRUE)": "C",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
result, err := f.CalcCellValue("Sheet1", "B10")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"HLOOKUP(INT(1),A3:A3,1,FALSE)": {"#N/A", "HLOOKUP no result found"},
"HLOOKUP(4,A1:E1048576,2,TRUE)": {"#N/A", "HLOOKUP no result found"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
result, err := f.CalcCellValue("Sheet1", "B10")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcCHITESTandCHISQdotTEST(t *testing.T) {
cellData := [][]interface{}{
{nil, "Observed Frequencies", nil, nil, "Expected Frequencies"},
{nil, "men", "women", nil, nil, "men", "women"},
{"answer a", 33, 39, nil, "answer a", 26.25, 31.5},
{"answer b", 62, 62, nil, "answer b", 57.75, 61.95},
{"answer c", 10, 4, nil, "answer c", 21, 11.55},
{nil, -1, 0},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"CHITEST(B3:C5,F3:G5)": "0.000699102758787672",
"CHITEST(B3:C3,F3:G3)": "0.0605802098655177",
"CHITEST(B3:B4,F3:F4)": "0.152357748933542",
"CHITEST(B4:B6,F3:F5)": "7.07076951440726E-25",
"CHISQ.TEST(B3:C5,F3:G5)": "0.000699102758787672",
"CHISQ.TEST(B3:C3,F3:G3)": "0.0605802098655177",
"CHISQ.TEST(B3:B4,F3:F4)": "0.152357748933542",
"CHISQ.TEST(B4:B6,F3:F5)": "7.07076951440726E-25",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "I1", formula))
result, err := f.CalcCellValue("Sheet1", "I1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"CHITEST()": {"#VALUE!", "CHITEST requires 2 arguments"},
"CHITEST(MUNIT(0),MUNIT(0))": {"#VALUE!", "#VALUE!"},
"CHITEST(B3:C5,F3:F4)": {"#N/A", "#N/A"},
"CHITEST(B3:B3,F3:F3)": {"#N/A", "#N/A"},
"CHITEST(F3:F5,B4:B6)": {"#NUM!", "#NUM!"},
"CHITEST(F3:F5,C4:C6)": {"#DIV/0!", "#DIV/0!"},
"CHISQ.TEST()": {"#VALUE!", "CHISQ.TEST requires 2 arguments"},
"CHISQ.TEST(B3:C5,F3:F4)": {"#N/A", "#N/A"},
"CHISQ.TEST(B3:B3,F3:F3)": {"#N/A", "#N/A"},
"CHISQ.TEST(F3:F5,B4:B6)": {"#NUM!", "#NUM!"},
"CHISQ.TEST(F3:F5,C4:C6)": {"#DIV/0!", "#DIV/0!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "I1", formula))
result, err := f.CalcCellValue("Sheet1", "I1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcFTEST(t *testing.T) {
cellData := [][]interface{}{
{"Group 1", "Group 2"},
{3.5, 9.2},
{4.7, 8.2},
{6.2, 7.3},
{4.9, 6.1},
{3.8, 5.4},
{5.5, 7.8},
{7.1, 5.9},
{6.7, 8.4},
{3.9, 7.7},
{4.6, 6.6},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"FTEST(A2:A11,B2:B11)": "0.95403555939413",
"F.TEST(A2:A11,B2:B11)": "0.95403555939413",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"FTEST()": {"#VALUE!", "FTEST requires 2 arguments"},
"FTEST(A2:A2,B2:B2)": {"#DIV/0!", "#DIV/0!"},
"FTEST(A12:A14,B2:B4)": {"#DIV/0!", "#DIV/0!"},
"FTEST(A2:A4,B2:B2)": {"#DIV/0!", "#DIV/0!"},
"FTEST(A2:A4,B12:B14)": {"#DIV/0!", "#DIV/0!"},
"F.TEST()": {"#VALUE!", "F.TEST requires 2 arguments"},
"F.TEST(A2:A2,B2:B2)": {"#DIV/0!", "#DIV/0!"},
"F.TEST(A12:A14,B2:B4)": {"#DIV/0!", "#DIV/0!"},
"F.TEST(A2:A4,B2:B2)": {"#DIV/0!", "#DIV/0!"},
"F.TEST(A2:A4,B12:B14)": {"#DIV/0!", "#DIV/0!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcIRR(t *testing.T) {
cellData := [][]interface{}{{-1}, {0.2}, {0.24}, {0.288}, {0.3456}, {0.4147}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"IRR(A1:A4)": "-0.136189509034157",
"IRR(A1:A6)": "0.130575760006905",
"IRR(A1:A4,-0.1)": "-0.136189514994621",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"IRR()": {"#VALUE!", "IRR requires at least 1 argument"},
"IRR(0,0,0)": {"#VALUE!", "IRR allows at most 2 arguments"},
"IRR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"IRR(A2:A3)": {"#NUM!", "#NUM!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcMAXMINIFS(t *testing.T) {
f := NewFile()
for cell, row := range map[string][]interface{}{
"A1": {1, -math.MaxFloat64 - 1},
"A2": {2, -math.MaxFloat64 - 2},
"A3": {3, math.MaxFloat64 + 1},
"A4": {4, math.MaxFloat64 + 2},
} {
assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row))
}
formulaList := map[string]string{
"MAX(B1:B2)": "0",
"MAXIFS(B1:B2,A1:A2,\">0\")": "0",
"MIN(B3:B4)": "0",
"MINIFS(B3:B4,A3:A4,\"<0\")": "0",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcMIRR(t *testing.T) {
cellData := [][]interface{}{{-100}, {18}, {22.5}, {28}, {35.5}, {45}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"MIRR(A1:A6,0.055,0.05)": "0.1000268752662",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"MIRR()": {"#VALUE!", "MIRR requires 3 arguments"},
"MIRR(A1:A5,\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MIRR(A1:A5,0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"MIRR(B1:B5,0,0)": {"#DIV/0!", "#DIV/0!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcSUMIFSAndAVERAGEIFS(t *testing.T) {
cellData := [][]interface{}{
{"Quarter", "Area", "Sales Rep.", "Sales"},
{1, "North", "Jeff", 223000},
{1, "North", "Chris", 125000},
{1, "South", "Carol", 456000},
{2, "North", "Jeff", 322000},
{2, "North", "Chris", 340000},
{2, "South", "Carol", 198000},
{3, "North", "Jeff", 310000},
{3, "North", "Chris", 250000},
{3, "South", "Carol", 460000},
{4, "North", "Jeff", 261000},
{4, "North", "Chris", 389000},
{4, "South", "Carol", 305000},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"AVERAGEIFS(D2:D13,A2:A13,1,B2:B13,\"North\")": "174000",
"AVERAGEIFS(D2:D13,A2:A13,\">2\",C2:C13,\"Jeff\")": "285500",
"SUMIFS(D2:D13,A2:A13,1,B2:B13,\"North\")": "348000",
"SUMIFS(D2:D13,A2:A13,\">2\",C2:C13,\"Jeff\")": "571000",
"SUMIFS(D2:D13,A2:A13,1,D2:D13,125000)": "125000",
"SUMIFS(D2:D13,A2:A13,1,D2:D13,\">100000\",C2:C13,\"Chris\")": "125000",
"SUMIFS(D2:D13,A2:A13,1,D2:D13,\"<40000\",C2:C13,\"Chris\")": "0",
"SUMIFS(D2:D13,A2:A13,1,A2:A13,2)": "0",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"AVERAGEIFS()": {"#VALUE!", "AVERAGEIFS requires at least 3 arguments"},
"AVERAGEIFS(H1,\"\")": {"#VALUE!", "AVERAGEIFS requires at least 3 arguments"},
"AVERAGEIFS(H1,\"\",TRUE,1)": {"#N/A", "#N/A"},
"AVERAGEIFS(H1,\"\",TRUE)": {"#DIV/0!", "AVERAGEIF divide by zero"},
"AVERAGEIFS(D2:D13,A2:A13,1,A2:A13,2)": {"#DIV/0!", "AVERAGEIF divide by zero"},
"SUMIFS()": {"#VALUE!", "SUMIFS requires at least 3 arguments"},
"SUMIFS(D2:D13,A2:A13,1,B2:B13)": {"#N/A", "#N/A"},
"SUMIFS(D20:D23,A2:A13,\">2\",C2:C13,\"Jeff\")": {"#VALUE!", "#VALUE!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcSUMIFExactMatch(t *testing.T) {
cellData := [][]interface{}{
{"Category", "Amount"},
{"text", 100},
{"***_***_text", 200},
{"text", 150},
{"***_text_***", 300},
{"TEXT", 50},
{"other", 400},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
`SUMIF(A2:A7,"text",B2:B7)`: "300",
`COUNTIF(A2:A7,"text")`: "3",
`SUMIF(A2:A7,".",B2:B7)`: "0",
`SUMIF(A2:A7,".*",B2:B7)`: "0",
`SUMIF(A2:A7,".?",B2:B7)`: "0",
`SUMIF(A2:A7,"*text*",B2:B7)`: "800",
`SUMIF(A2:A7,"text*",B2:B7)`: "300",
`SUMIF(A2:A7,"*text",B2:B7)`: "500",
`SUMIF(A2:A7,"*",B2:B7)`: "1200",
`SUMIF(A2:A7,"othe?",B2:B7)`: "400",
`SUMIF(A2:A7,"~**",B2:B7)`: "500",
`COUNTIF(A2:A7,"*text*")`: "5",
`COUNTIF(A2:A7,"~*")`: "0",
`COUNTIF(A2:A7,"~~")`: "0",
`COUNTIF(A2:A7,"~?")`: "0",
`COUNTIF(A2:A7,"*~**")`: "2",
`COUNTIF(A2:A7,"~*~*~*_~*~*~*_text")`: "1",
`COUNTIF(A2:A7,"~*~*~*_text_~*~*~*")`: "1",
`COUNTIF(A2:A7,"~a")`: "0",
`COUNTIF(A2:A7,"<>text")`: "3",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
cellData = [][]interface{}{
{"Category", "Amount"},
{"text", 10},
{"***text", 20},
{"text ***", 30},
{"TEXT", 40},
{5, 50},
{5.5, 60},
}
f = prepareCalcData(cellData)
for formula, expected := range map[string]string{
`SUMIF(A2:A7,"text",B2:B7)`: "50",
`SUMIF(A2:A7,"*text",B2:B7)`: "70",
`SUMIF(A2:A7,"text*",B2:B7)`: "80",
`SUMIF(A2:A7,5,B2:B7)`: "50",
`COUNTIF(A2:A7,"tex?")`: "2",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcXIRR(t *testing.T) {
cellData := [][]interface{}{
{-100.00, 42370},
{20.00, 42461},
{40.00, 42644},
{25.00, 42767},
{8.00, 42795},
{15.00, 42887},
{-1e-10, 42979},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"XIRR(A1:A4,B1:B4)": "-0.196743861298328",
"XIRR(A1:A6,B1:B6,0.5)": "0.0944390744445204",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"XIRR()": {"#VALUE!", "XIRR requires 2 or 3 arguments"},
"XIRR(A1:A4,B1:B4,-1)": {"#VALUE!", "XIRR requires guess > -1"},
"XIRR(\"\",B1:B4)": {"#VALUE!", "#VALUE!"},
"XIRR(A1:A4,\"\")": {"#VALUE!", "#VALUE!"},
"XIRR(A1:A4,B1:B4,\"\")": {"#NUM!", "#NUM!"},
"XIRR(A2:A6,B2:B6)": {"#NUM!", "#NUM!"},
"XIRR(A2:A7,B2:B7)": {"#NUM!", "#NUM!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcXLOOKUP(t *testing.T) {
cellData := [][]interface{}{
{},
{nil, nil, "Quarter", "Gross Profit", "Net profit", "Profit %"},
{nil, nil, "Qtr1", nil, 19342, 29.30},
{},
{nil, "Income Statement", "Qtr1", "Qtr2", "Qtr3", "Qtr4", "Total"},
{nil, "Total sales", 50000, 78200, 89500, 91250, 308.95},
{nil, "Cost of sales", -25000, -42050, -59450, -60450, -186950},
{nil, "Gross Profit", 25000, 36150, 30050, 30800, 122000},
{},
{nil, "Depreciation", -899, -791, -202, -412, -2304},
{nil, "Interest", -513, -853, -150, -956, -2472},
{nil, "Earnings before Tax", 23588, 34506, 29698, 29432, 117224},
{},
{nil, "Tax", -4246, -6211, -5346, -5298, 21100},
{},
{nil, "Net profit", 19342, 28295, 24352, 24134, 96124},
{nil, "Profit %", 0.293, 0.278, 0.234, 0.276, 0.269},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"SUM(XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2))": "87272.293",
"SUM(XLOOKUP($C3,$C5:$C5,$C6:$G6,NA(),0,-2))": "309258.95",
"SUM(XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,-2))": "87272.293",
"SUM(XLOOKUP($C3,$C5:$G5,$C6:$G17,NA(),0,2))": "87272.293",
"SUM(XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,2))": "244000",
"XLOOKUP(D2,$B6:$B17,C6:C17)": "25000",
"XLOOKUP(D2,$B6:$B17,XLOOKUP($C3,$C5:$G5,$C6:$G17))": "25000",
"XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),2)": "25000",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
result, err := f.CalcCellValue("Sheet1", "D3")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"XLOOKUP()": {"#VALUE!", "XLOOKUP requires at least 3 arguments"},
"XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": {"#VALUE!", "XLOOKUP allows at most 6 arguments"},
"XLOOKUP($C3,$C5,$C6,NA(),0,2)": {"#N/A", "#N/A"},
"XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": {"#VALUE!", "#VALUE!"},
"XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": {"#VALUE!", "#VALUE!"},
"XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": {"#VALUE!", "#VALUE!"},
"XLOOKUP(D2,$B6:$B17,$C6:$G16,NA(),0,2)": {"#VALUE!", "#VALUE!"},
"XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),3,2)": {"#VALUE!", "#VALUE!"},
"XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,0)": {"#VALUE!", "#VALUE!"},
"XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"XLOOKUP(D2,$B6:$B17,$C6:$G17,NA(),0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
result, err := f.CalcCellValue("Sheet1", "D3")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
cellData = [][]interface{}{
{"Salesperson", "Item", "Amont"},
{"B", "Apples", 30, 25, 15, 50, 45, 18},
{"L", "Oranges", 25, "D3", "E3"},
{"C", "Grapes", 15},
{"L", "Lemons", 50},
{"L", "Oranges", 45},
{"C", "Peaches", 18},
{"B", "Pears", 40},
{"B", "Apples", 55},
}
f = prepareCalcData(cellData)
formulaList = map[string]string{
// Test match mode with partial match (wildcards)
"XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),2)": "30",
// Test match mode with approximate match in vertical (next larger item)
"XLOOKUP(32,B2:B9,C2:C9,NA(),1)": "30",
// Test match mode with approximate match in horizontal (next larger item)
"XLOOKUP(30,C2:F2,C3:F3,NA(),1)": "25",
// Test match mode with approximate match in vertical (next smaller item)
"XLOOKUP(40,C2:C9,B2:B9,NA(),-1)": "Pears",
// Test match mode with approximate match in horizontal (next smaller item)
"XLOOKUP(29,C2:F2,C3:F3,NA(),-1)": "D3",
// Test search mode
"XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,1)": "25",
"XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,-1)": "45",
"XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,2)": "50",
"XLOOKUP(\"L\",A2:A9,C2:C9,NA(),0,-2)": "45",
// Test match mode and search mode
"XLOOKUP(29,C2:H2,C3:H3,NA(),-1,-1)": "D3",
"XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "D4", formula))
result, err := f.CalcCellValue("Sheet1", "D4")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError = map[string][]string{
// Test match mode with exact match
"XLOOKUP(\"*p*\",B2:B9,C2:C9,NA(),0)": {"#N/A", "#N/A"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
result, err := f.CalcCellValue("Sheet1", "D3")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcXNPV(t *testing.T) {
cellData := [][]interface{}{
{nil, 0.05},
{42370, -10000, nil},
{42401, 2000},
{42491, 2400},
{42552, 2900},
{42675, 3500},
{42736, 4100},
{},
{42401},
{42370},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"XNPV(B1,B2:B7,A2:A7)": "4447.93800944052",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"XNPV()": {"#VALUE!", "XNPV requires 3 arguments"},
"XNPV(\"\",B2:B7,A2:A7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"XNPV(0,B2:B7,A2:A7)": {"#VALUE!", "XNPV requires rate > 0"},
"XNPV(B1,\"\",A2:A7)": {"#VALUE!", "#VALUE!"},
"XNPV(B1,B2:B7,\"\")": {"#VALUE!", "#VALUE!"},
"XNPV(B1,B2:B7,C2:C7)": {"#VALUE!", "#VALUE!"},
"XNPV(B1,B2,A2)": {"#NUM!", "#NUM!"},
"XNPV(B1,B2:B3,A2:A5)": {"#NUM!", "#NUM!"},
"XNPV(B1,B2:B3,A9:A10)": {"#VALUE!", "#VALUE!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcMATCH(t *testing.T) {
f := NewFile()
for cell, row := range map[string][]interface{}{
"A1": {"cccc", 7, 4, 16},
"A2": {"dddd", 2, 6, 11},
"A3": {"aaaa", 4, 7, 10},
"A4": {"bbbb", 1, 10, 7},
"A5": {"eeee", 8, 11, 6},
"A6": {nil, 11, 16, 4},
} {
assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row))
}
formulaList := map[string]string{
"MATCH(\"aaaa\",A1:A6,0)": "3",
"MATCH(\"*b\",A1:A5,0)": "4",
"MATCH(\"?eee\",A1:A5,0)": "5",
"MATCH(\"?*?e\",A1:A5,0)": "5",
"MATCH(\"aaaa\",A1:A6,1)": "3",
"MATCH(10,B1:B6)": "5",
"MATCH(8,C1:C6,1)": "3",
"MATCH(6,B1:B6,-1)": "1",
"MATCH(10,D1:D6,-1)": "3",
"MATCH(-10,D1:D6,-1)": "6",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string]string{
"MATCH(3,C1:C6,1)": "#N/A",
"MATCH(5,C1:C6,-1)": "#N/A",
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.EqualError(t, err, expected, formula)
assert.Equal(t, expected, result, formula)
}
assert.Equal(t, newErrorFormulaArg(formulaErrorNA, formulaErrorNA), calcMatch(2, nil, []formulaArg{}))
}
func TestCalcISFORMULA(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "ISFORMULA(A1)"))
for _, formula := range []string{"NA()", "SUM(A1:A3)"} {
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.NoError(t, err, formula)
assert.Equal(t, "TRUE", result, formula)
}
}
func TestCalcMODE(t *testing.T) {
cellData := [][]interface{}{
{1, 1},
{1, 1},
{2, 2},
{2, 2},
{3, 2},
{3},
{3},
{4},
{4},
{4},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"MODE(A1:A10)": "3",
"MODE(B1:B6)": "2",
"MODE.MULT(A1:A10)": "3",
"MODE.SNGL(A1:A10)": "3",
"MODE.SNGL(B1:B6)": "2",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"MODE()": {"#VALUE!", "MODE requires at least 1 argument"},
"MODE(0,\"\")": {"#VALUE!", "#VALUE!"},
"MODE(D1:D3)": {"#N/A", "#N/A"},
"MODE.MULT()": {"#VALUE!", "MODE.MULT requires at least 1 argument"},
"MODE.MULT(0,\"\")": {"#VALUE!", "#VALUE!"},
"MODE.MULT(D1:D3)": {"#N/A", "#N/A"},
"MODE.SNGL()": {"#VALUE!", "MODE.SNGL requires at least 1 argument"},
"MODE.SNGL(0,\"\")": {"#VALUE!", "#VALUE!"},
"MODE.SNGL(D1:D3)": {"#N/A", "#N/A"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcPEARSON(t *testing.T) {
cellData := [][]interface{}{
{"x", "y"},
{1, 10.11},
{2, 22.9},
{2, 27.61},
{3, 27.61},
{4, 11.15},
{5, 31.08},
{6, 37.9},
{7, 33.49},
{8, 21.05},
{9, 27.01},
{10, 45.78},
{11, 31.32},
{12, 50.57},
{13, 45.48},
{14, 40.94},
{15, 53.76},
{16, 36.18},
{17, 49.77},
{18, 55.66},
{19, 63.83},
{20, 63.6},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"PEARSON(A2:A22,B2:B22)": "0.864129542184994",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcPROB(t *testing.T) {
cellData := [][]interface{}{
{"x", "probability"},
{0, 0.1},
{1, 0.15},
{2, 0.17},
{3, 0.22},
{4, 0.21},
{5, 0.09},
{6, 0.05},
{7, 0.01},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"PROB(A2:A9,B2:B9,3)": "0.22",
"PROB(A2:A9,B2:B9,3,5)": "0.52",
"PROB(A2:A9,B2:B9,8,10)": "0",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "NA()"))
calcError := map[string][]string{
"PROB(A2:A9,B2:B9,3)": {"#NUM!", "#NUM!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcRSQ(t *testing.T) {
cellData := [][]interface{}{
{"known_y's", "known_x's"},
{2, 22.9},
{7, 33.49},
{8, 34.5},
{3, 27.61},
{4, 19.5},
{1, 10.11},
{6, 37.9},
{5, 31.08},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"RSQ(A2:A9,B2:B9)": "0.711666290486784",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcSLOP(t *testing.T) {
cellData := [][]interface{}{
{"known_x's", "known_y's"},
{1, 3},
{2, 7},
{3, 17},
{4, 20},
{5, 20},
{6, 27},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"SLOPE(A2:A7,B2:B7)": "0.200826446280992",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcSHEET(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{
"SHEET(\"Sheet2\")": "2",
"SHEET(Sheet2!A1)": "2",
"SHEET(Sheet2!A1:A2)": "2",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula))
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcSHEETS(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{
"SHEETS(Sheet1!A1:B1)": "1",
"SHEETS(Sheet1!A1:Sheet1!B1)": "1",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula))
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcSTEY(t *testing.T) {
cellData := [][]interface{}{
{"known_x's", "known_y's"},
{1, 3},
{2, 7.9},
{3, 8},
{4, 9.2},
{4.5, 12},
{5, 10.5},
{6, 15},
{7, 15.5},
{8, 17},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"STEYX(B2:B11,A2:A11)": "1.20118634668221",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"STEYX()": {"#VALUE!", "STEYX requires 2 arguments"},
"STEYX(B2:B11,A1:A9)": {"#N/A", "#N/A"},
"STEYX(B2,A2)": {"#DIV/0!", "#DIV/0!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcTTEST(t *testing.T) {
cellData := [][]interface{}{
{4, 8, nil, 1, 1},
{5, 3, nil, 1, 1},
{2, 7},
{5, 3},
{8, 5},
{9, 2},
{3, 2},
{2, 7},
{3, 9},
{8, 4},
{9, 4},
{5, 7},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"TTEST(A1:A12,B1:B12,1,1)": "0.44907068944428",
"TTEST(A1:A12,B1:B12,1,2)": "0.436717306029283",
"TTEST(A1:A12,B1:B12,1,3)": "0.436722015384755",
"TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559",
"TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567",
"TTEST(A1:A12,B1:B12,2,3)": "0.873444030769511",
"T.TEST(A1:A12,B1:B12,1,1)": "0.44907068944428",
"T.TEST(A1:A12,B1:B12,1,2)": "0.436717306029283",
"T.TEST(A1:A12,B1:B12,1,3)": "0.436722015384755",
"T.TEST(A1:A12,B1:B12,2,1)": "0.898141378888559",
"T.TEST(A1:A12,B1:B12,2,2)": "0.873434612058567",
"T.TEST(A1:A12,B1:B12,2,3)": "0.873444030769511",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"TTEST()": {"#VALUE!", "TTEST requires 4 arguments"},
"TTEST(\"\",B1:B12,1,1)": {"#NUM!", "#NUM!"},
"TTEST(A1:A12,\"\",1,1)": {"#NUM!", "#NUM!"},
"TTEST(A1:A12,B1:B12,\"\",1)": {"#VALUE!", "#VALUE!"},
"TTEST(A1:A12,B1:B12,1,\"\")": {"#VALUE!", "#VALUE!"},
"TTEST(A1:A12,B1:B12,0,1)": {"#NUM!", "#NUM!"},
"TTEST(A1:A12,B1:B12,1,0)": {"#NUM!", "#NUM!"},
"TTEST(A1:A2,B1:B1,1,1)": {"#N/A", "#N/A"},
"TTEST(A13:A14,B13:B14,1,1)": {"#NUM!", "#NUM!"},
"TTEST(A12:A13,B12:B13,1,1)": {"#DIV/0!", "#DIV/0!"},
"TTEST(A13:A14,B13:B14,1,2)": {"#NUM!", "#NUM!"},
"TTEST(D1:D4,E1:E4,1,3)": {"#NUM!", "#NUM!"},
"T.TEST()": {"#VALUE!", "T.TEST requires 4 arguments"},
"T.TEST(\"\",B1:B12,1,1)": {"#NUM!", "#NUM!"},
"T.TEST(A1:A12,\"\",1,1)": {"#NUM!", "#NUM!"},
"T.TEST(A1:A12,B1:B12,\"\",1)": {"#VALUE!", "#VALUE!"},
"T.TEST(A1:A12,B1:B12,1,\"\")": {"#VALUE!", "#VALUE!"},
"T.TEST(A1:A12,B1:B12,0,1)": {"#NUM!", "#NUM!"},
"T.TEST(A1:A12,B1:B12,1,0)": {"#NUM!", "#NUM!"},
"T.TEST(A1:A2,B1:B1,1,1)": {"#N/A", "#N/A"},
"T.TEST(A13:A14,B13:B14,1,1)": {"#NUM!", "#NUM!"},
"T.TEST(A12:A13,B12:B13,1,1)": {"#DIV/0!", "#DIV/0!"},
"T.TEST(A13:A14,B13:B14,1,2)": {"#NUM!", "#NUM!"},
"T.TEST(D1:D4,E1:E4,1,3)": {"#NUM!", "#NUM!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) {
cellData := [][]interface{}{
{"05/01/2019", 43586, "text1"},
{"09/13/2019", 43721, "text2"},
{"10/01/2019", 43739},
{"12/25/2019", 43824},
{"01/01/2020", 43831},
{"01/01/2020", 43831},
{"01/24/2020", 43854},
{"04/04/2020", 43925},
{"05/01/2020", 43952},
{"06/25/2020", 44007},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"NETWORKDAYS(\"01/01/2020\",\"09/12/2020\")": "183",
"NETWORKDAYS(\"01/01/2020\",\"09/12/2020\",2)": "183",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\")": "183",
"NETWORKDAYS.INTL(\"09/12/2020\",\"01/01/2020\")": "-183",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1)": "183",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",2)": "184",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",3)": "184",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",4)": "183",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",5)": "182",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",6)": "182",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",7)": "182",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",11)": "220",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",12)": "220",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",13)": "220",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",14)": "219",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",15)": "219",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",16)": "219",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",17)": "219",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,A1:A12)": "178",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,B1:B12)": "178",
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,C1:C2)": "183",
"WORKDAY(\"12/01/2015\",25)": "42374",
"WORKDAY(\"01/01/2020\",123,B1:B12)": "44006",
"WORKDAY.INTL(\"12/01/2015\",0)": "42339",
"WORKDAY.INTL(\"12/01/2015\",25)": "42374",
"WORKDAY.INTL(\"12/01/2015\",-25)": "42304",
"WORKDAY.INTL(\"12/01/2015\",25,1)": "42374",
"WORKDAY.INTL(\"12/01/2015\",25,2)": "42374",
"WORKDAY.INTL(\"12/01/2015\",25,3)": "42372",
"WORKDAY.INTL(\"12/01/2015\",25,4)": "42373",
"WORKDAY.INTL(\"12/01/2015\",25,5)": "42374",
"WORKDAY.INTL(\"12/01/2015\",25,6)": "42374",
"WORKDAY.INTL(\"12/01/2015\",25,7)": "42374",
"WORKDAY.INTL(\"12/01/2015\",25,11)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,12)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,13)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,14)": "42369",
"WORKDAY.INTL(\"12/01/2015\",25,15)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,16)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,17)": "42368",
"WORKDAY.INTL(\"12/01/2015\",25,\"0001100\")": "42374",
"WORKDAY.INTL(\"01/01/2020\",-123,4)": "43659",
"WORKDAY.INTL(\"01/01/2020\",123,4,44010)": "44002",
"WORKDAY.INTL(\"01/01/2020\",-123,4,43640)": "43659",
"WORKDAY.INTL(\"01/01/2020\",-123,4,43660)": "43658",
"WORKDAY.INTL(\"01/01/2020\",-123,7,43660)": "43657",
"WORKDAY.INTL(\"01/01/2020\",123,4,A1:A12)": "44008",
"WORKDAY.INTL(\"01/01/2020\",123,4,B1:B12)": "44008",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"NETWORKDAYS()": {"#VALUE!", "NETWORKDAYS requires at least 2 arguments"},
"NETWORKDAYS(\"01/01/2020\",\"09/12/2020\",2,\"\")": {"#VALUE!", "NETWORKDAYS requires at most 3 arguments"},
"NETWORKDAYS(\"\",\"09/12/2020\",2)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS(\"01/01/2020\",\"\",2)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL()": {"#VALUE!", "NETWORKDAYS.INTL requires at least 2 arguments"},
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",4,A1:A12,\"\")": {"#VALUE!", "NETWORKDAYS.INTL requires at most 4 arguments"},
"NETWORKDAYS.INTL(\"01/01/2020\",\"January 25, 100\",4)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(\"\",123,4,B1:B12)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(\"01/01/2020\",123,\"000000x\")": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(\"01/01/2020\",123,\"0000002\")": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",8)": {"#VALUE!", "#VALUE!"},
"NETWORKDAYS.INTL(-1,123)": {"#NUM!", "#NUM!"},
"WORKDAY()": {"#VALUE!", "WORKDAY requires at least 2 arguments"},
"WORKDAY(\"01/01/2020\",123,A1:A12,\"\")": {"#VALUE!", "WORKDAY requires at most 3 arguments"},
"WORKDAY(\"01/01/2020\",\"\",B1:B12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"WORKDAY(\"\",123,B1:B12)": {"#VALUE!", "#VALUE!"},
"WORKDAY(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"},
"WORKDAY(-1,123)": {"#NUM!", "#NUM!"},
"WORKDAY.INTL()": {"#VALUE!", "WORKDAY.INTL requires at least 2 arguments"},
"WORKDAY.INTL(\"01/01/2020\",123,4,A1:A12,\"\")": {"#VALUE!", "WORKDAY.INTL requires at most 4 arguments"},
"WORKDAY.INTL(\"01/01/2020\",\"\",4,B1:B12)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"WORKDAY.INTL(\"\",123,4,B1:B12)": {"#VALUE!", "#VALUE!"},
"WORKDAY.INTL(\"01/01/2020\",123,\"\",B1:B12)": {"#VALUE!", "#VALUE!"},
"WORKDAY.INTL(\"01/01/2020\",123,\"000000x\")": {"#VALUE!", "#VALUE!"},
"WORKDAY.INTL(\"01/01/2020\",123,\"0000002\")": {"#VALUE!", "#VALUE!"},
"WORKDAY.INTL(\"January 25, 100\",123)": {"#VALUE!", "#VALUE!"},
"WORKDAY.INTL(-1,123)": {"#NUM!", "#NUM!"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.Equal(t, expected[0], result, formula)
assert.EqualError(t, err, expected[1], formula)
}
}
func TestCalcZTEST(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{4, 5, 2, 5, 8, 9, 3, 2, 3, 8, 9, 5}))
formulaList := map[string]string{
"Z.TEST(A1:L1,5)": "0.371103278558538",
"Z.TEST(A1:L1,6)": "0.838129187019751",
"Z.TEST(A1:L1,5,1)": "0.193238115385616",
"ZTEST(A1:L1,5)": "0.371103278558538",
"ZTEST(A1:L1,6)": "0.838129187019751",
"ZTEST(A1:L1,5,1)": "0.193238115385616",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "M1", formula))
result, err := f.CalcCellValue("Sheet1", "M1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestStrToDate(t *testing.T) {
_, _, _, _, err := strToDate("")
assert.Equal(t, formulaErrorVALUE, err.Error)
}
func TestGetYearDays(t *testing.T) {
for _, data := range [][]int{{2021, 0, 360}, {2000, 1, 366}, {2021, 1, 365}, {2000, 3, 365}} {
assert.Equal(t, data[2], getYearDays(data[0], data[1]))
}
}
func TestCalcGetBetaHelperContFrac(t *testing.T) {
assert.Equal(t, 1.0, getBetaHelperContFrac(1, 0, 1))
}
func TestCalcGetBetaDistPDF(t *testing.T) {
assert.Equal(t, 0.0, getBetaDistPDF(0.5, 2000, 3))
assert.Equal(t, 0.0, getBetaDistPDF(0, 1, 0))
}
func TestCalcD1mach(t *testing.T) {
assert.Equal(t, 0.0, d1mach(6))
}
func TestCalcChebyshevInit(t *testing.T) {
assert.Equal(t, 0, chebyshevInit(0, 0, nil))
assert.Equal(t, 0, chebyshevInit(1, 0, []float64{0}))
}
func TestCalcChebyshevEval(t *testing.T) {
assert.True(t, math.IsNaN(chebyshevEval(0, 0, nil)))
}
func TestCalcLgammacor(t *testing.T) {
assert.True(t, math.IsNaN(lgammacor(9)))
assert.Equal(t, 4.930380657631324e-32, lgammacor(3.7451940309632633e+306))
assert.Equal(t, 8.333333333333334e-10, lgammacor(10e+07))
}
func TestCalcLgammaerr(t *testing.T) {
assert.True(t, math.IsNaN(logrelerr(-2)))
}
func TestCalcLogBeta(t *testing.T) {
assert.True(t, math.IsNaN(logBeta(-1, -1)))
assert.Equal(t, math.MaxFloat64, logBeta(0, 0))
}
func TestCalcBetainvProbIterator(t *testing.T) {
assert.Equal(t, 1.0, betainvProbIterator(1, 1, 1, 1, 1, 1, 1, 1, 1))
}
func TestCalcRangeResolver(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "SUM(Sheet1!B:B)"))
cellRefs := list.New()
cellRanges := list.New()
// Test extract value from ranges on invalid ranges
cellRanges.PushBack(cellRange{
From: cellRef{Col: 1, Row: 1, Sheet: "SheetN"},
To: cellRef{Col: 1, Row: TotalRows, Sheet: "SheetN"},
})
_, err := f.rangeResolver(&calcContext{}, cellRefs, cellRanges)
assert.EqualError(t, err, "sheet SheetN does not exist")
ws, err := f.workSheetReader("Sheet1")
ws.SheetData.Row = make([]xlsxRow, TotalRows+1)
ws.SheetData.Row[TotalRows].C = make([]xlsxC, 3)
assert.NoError(t, err)
cellRanges.Init()
cellRanges.PushBack(cellRange{
From: cellRef{Col: 3, Row: TotalRows, Sheet: "Sheet1"},
To: cellRef{Col: 3, Row: TotalRows + 1, Sheet: "Sheet1"},
})
_, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges)
assert.Equal(t, ErrMaxRows, err)
// Test extract value from references with invalid references
cellRanges.Init()
cellRefs.PushBack(cellRef{Col: 1, Row: 1, Sheet: "SheetN"})
_, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges)
assert.EqualError(t, err, "sheet SheetN does not exist")
cellRefs.Init()
cellRefs.PushBack(cellRef{Col: 1, Row: TotalRows + 1, Sheet: "SheetN"})
_, err = f.rangeResolver(&calcContext{}, cellRefs, cellRanges)
assert.Equal(t, ErrMaxRows, err)
t.Run("for_range_resolver_error", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "test"))
cellRefs := list.New()
cellRanges := list.New()
cellRanges.PushBack(cellRange{
From: cellRef{Col: 1, Row: 1, Sheet: "Sheet1"},
To: cellRef{Col: 1, Row: 1, Sheet: "Sheet1"},
})
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err := f.rangeResolver(&calcContext{}, cellRefs, cellRanges)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
})
}
func TestCalcBahttextAppendDigit(t *testing.T) {
assert.Empty(t, bahttextAppendDigit("", -1))
}
func TestNestedFunctionsWithOperators(t *testing.T) {
f := NewFile()
formulaList := map[string]string{
"LEN(\"KEEP\")": "4",
"LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\")": "4",
"RIGHT(\"REMOVEKEEP\", 4)": "KEEP",
"RIGHT(\"REMOVEKEEP\", 10 - 6))": "KEEP",
"RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - 6)": "KEEP",
"RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOV\") - 1)": "KEEP",
"RIGHT(\"REMOVEKEEP\", 10 - LEN(\"REMOVE\"))": "KEEP",
"RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\"))": "KEEP",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestFormulaRawCellValueOption(t *testing.T) {
f := NewFile()
rawTest := []struct {
value string
raw bool
expected string
}{
{"=VALUE(\"1.0E-07\")", false, "0.00"},
{"=VALUE(\"1.0E-07\")", true, "0.0000001"},
{"=\"text\"", false, "$text"},
{"=\"text\"", true, "text"},
{"=\"10e3\"", false, "$10e3"},
{"=\"10e3\"", true, "10e3"},
{"=\"10\" & \"e3\"", false, "$10e3"},
{"=\"10\" & \"e3\"", true, "10e3"},
{"=10e3", false, "10000.00"},
{"=10e3", true, "10000"},
{"=\"1111111111111111\"", false, "$1111111111111111"},
{"=\"1111111111111111\"", true, "1111111111111111"},
{"=1111111111111111", false, "1111111111111110.00"},
{"=1111111111111111", true, "1.11111111111111E+15"},
{"=1444.00000000003", false, "1444.00"},
{"=1444.00000000003", true, "1444.00000000003"},
{"=1444.000000000003", false, "1444.00"},
{"=1444.000000000003", true, "1444"},
{"=ROUND(1444.00000000000003,2)", false, "1444.00"},
{"=ROUND(1444.00000000000003,2)", true, "1444"},
}
exp := "0.00;0.00;;$@"
styleID, err := f.NewStyle(&Style{CustomNumFmt: &exp})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID))
for _, test := range rawTest {
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", test.value))
val, err := f.CalcCellValue("Sheet1", "A1", Options{RawCellValue: test.raw})
assert.NoError(t, err)
assert.Equal(t, test.expected, val)
}
}
func TestFormulaArgToToken(t *testing.T) {
assert.Equal(t,
efp.Token{
TType: efp.TokenTypeOperand,
TSubType: efp.TokenSubTypeLogical,
TValue: "TRUE",
},
formulaArgToToken(newBoolFormulaArg(true)),
)
}
func TestPrepareTrendGrowth(t *testing.T) {
assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxX([][]float64{{0, 0}, {0, 0}}))
assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxY(false, [][]float64{{0, 0}, {0, 0}}))
info, err := prepareTrendGrowth(false, [][]float64{{0, 0}, {0, 0}}, [][]float64{{0, 0}, {0, 0}})
assert.Nil(t, info)
assert.Equal(t, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM), err)
}
func TestCalcColRowQRDecomposition(t *testing.T) {
assert.False(t, calcRowQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0))
assert.False(t, calcColQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0))
}
func TestCalcCellResolver(t *testing.T) {
f := NewFile()
// Test reference a cell multiple times in a formula
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "VALUE1"))
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "A1"))
for formula, expected := range map[string]string{
"CONCATENATE(A1,\"_\",A1)": "VALUE1_VALUE1",
"CONCATENATE(A1,\"_\",A2)": "VALUE1_VALUE1",
"CONCATENATE(A2,\"_\",A2)": "VALUE1_VALUE1",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", formula))
result, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
// Test calculates formula that contains a nested argument function which returns a numeric result
f = NewFile()
for _, cell := range []string{"A1", "B2", "B3", "B4"} {
assert.NoError(t, f.SetCellValue("Sheet1", cell, "ABC"))
}
for cell, formula := range map[string]string{
"A2": "IF(B2<>\"\",MAX(A1:A1)+1,\"\")",
"A3": "IF(B3<>\"\",MAX(A1:A2)+1,\"\")",
"A4": "IF(B4<>\"\",MAX(A1:A3)+1,\"\")",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, formula))
}
for cell, expected := range map[string]string{"A2": "1", "A3": "2", "A4": "3"} {
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
// Test calculates formula that reference date and error type cells
assert.NoError(t, f.SetCellValue("Sheet1", "C1", "20200208T080910.123"))
assert.NoError(t, f.SetCellValue("Sheet1", "C2", "2020-07-10 15:00:00.000"))
assert.NoError(t, f.SetCellValue("Sheet1", "C3", formulaErrorDIV))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[2].T = "d"
ws.(*xlsxWorksheet).SheetData.Row[0].C[2].V = "20200208T080910.123"
ws.(*xlsxWorksheet).SheetData.Row[1].C[2].T = "d"
ws.(*xlsxWorksheet).SheetData.Row[1].C[2].V = "2020-07-10 15:00:00.000"
ws.(*xlsxWorksheet).SheetData.Row[2].C[2].T = "e"
ws.(*xlsxWorksheet).SheetData.Row[2].C[2].V = formulaErrorDIV
for _, tbl := range [][]string{
{"D1", "SUM(C1,1)", "43870.3397004977"},
{"D2", "LEN(C2)", "23"},
{"D3", "IFERROR(C3,TRUE)", "TRUE"},
} {
assert.NoError(t, f.SetCellFormula("Sheet1", tbl[0], tbl[1]))
result, err := f.CalcCellValue("Sheet1", tbl[0])
assert.NoError(t, err)
assert.Equal(t, tbl[2], result)
}
// Test calculates formula that reference invalid cell
assert.NoError(t, f.SetCellValue("Sheet1", "E1", "E1"))
assert.NoError(t, f.SetCellFormula("Sheet1", "F1", "LEN(E1)"))
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err := f.CalcCellValue("Sheet1", "F1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestEvalInfixExp(t *testing.T) {
f := NewFile()
arg, err := f.evalInfixExp(nil, "Sheet1", "A1", []efp.Token{
{TSubType: efp.TokenSubTypeRange, TValue: "1A"},
})
assert.Equal(t, arg, newEmptyFormulaArg())
assert.Equal(t, formulaErrorNAME, err.Error())
}
func TestParseToken(t *testing.T) {
f := NewFile()
assert.Equal(t, formulaErrorNAME, f.parseToken(nil, "Sheet1",
efp.Token{TSubType: efp.TokenSubTypeRange, TValue: "1A"}, nil, nil,
).Error())
}
func TestCalcCellValueCache(t *testing.T) {
t.Run("for_calc_call_value_with_cache", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 40))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 50))
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2"))
result1, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, "90", result1)
result2, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, result1, result2, "cached result should be consistent")
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 60))
result3, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, "110", result3)
assert.NotEqual(t, result1, result3, "result should be updated after cache clear")
})
t.Run("for_calc_call_value_with_multiple_dependent_cells", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 10))
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2"))
assert.NoError(t, f.SetCellFormula("Sheet1", "A4", "A3*3"))
assert.NoError(t, f.SetCellFormula("Sheet1", "A5", "A3+A4"))
result3, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, "20", result3)
result4, err := f.CalcCellValue("Sheet1", "A4")
assert.NoError(t, err)
assert.Equal(t, "60", result4)
result5, err := f.CalcCellValue("Sheet1", "A5")
assert.NoError(t, err)
assert.Equal(t, "80", result5)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 20))
newResult3, err := f.CalcCellValue("Sheet1", "A3")
assert.NoError(t, err)
assert.Equal(t, "30", newResult3)
assert.NotEqual(t, result3, newResult3, "A3 should be updated")
newResult5, err := f.CalcCellValue("Sheet1", "A5")
assert.NoError(t, err)
assert.Equal(t, "120", newResult5)
assert.NotEqual(t, result5, newResult5, "A5 should be updated")
})
t.Run("for_clear_calculation_cache", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10))
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "A1*2"))
result1, err := f.CalcCellValue("Sheet1", "A2")
assert.NoError(t, err)
assert.Equal(t, "20", result1)
result2, err := f.CalcCellValue("Sheet1", "A2")
assert.NoError(t, err)
assert.Equal(t, result1, result2, "results should be consistent from cache")
cases := []struct {
name string
fn func() error
}{
{"SetCellValue", func() error { return f.SetCellValue("Sheet1", "B1", 100) }},
{"SetCellInt", func() error { return f.SetCellInt("Sheet1", "B2", 200) }},
{"SetCellUint", func() error { return f.SetCellUint("Sheet1", "B3", 300) }},
{"SetCellFloat", func() error { return f.SetCellFloat("Sheet1", "B4", 3.14, 2, 64) }},
{"SetCellStr", func() error { return f.SetCellStr("Sheet1", "B5", "test") }},
{"SetCellBool", func() error { return f.SetCellBool("Sheet1", "B6", true) }},
{"SetCellDefault", func() error { return f.SetCellDefault("Sheet1", "B7", "default") }},
{"SetCellFormula", func() error { return f.SetCellFormula("Sheet1", "B8", "=1+1") }},
{"SetCellHyperLink", func() error {
return f.SetCellHyperLink("Sheet1", "B9", "https://github.com/xuri/excelize", "External")
}},
{"SetCellRichText", func() error {
runs := []RichTextRun{{Text: "Rich", Font: &Font{Bold: true}}}
return f.SetCellRichText("Sheet1", "B10", runs)
}},
{"SetSheetRow", func() error { return f.SetSheetRow("Sheet1", "C1", &[]interface{}{1, 2, 3}) }},
{"SetSheetCol", func() error { return f.SetSheetCol("Sheet1", "D1", &[]interface{}{4, 5, 6}) }},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := f.CalcCellValue("Sheet1", "A2")
assert.NoError(t, err)
assert.NoError(t, tc.fn())
result, err := f.CalcCellValue("Sheet1", "A2")
assert.NoError(t, err)
assert.Equal(t, "20", result, "calculation should still work after cache clear")
})
}
})
}
func TestCalcLookupCol(t *testing.T) {
result := lookupCol(formulaArg{
Type: ArgMatrix,
Matrix: [][]formulaArg{
{newNumberFormulaArg(1), newNumberFormulaArg(2)},
{},
{newNumberFormulaArg(3), newNumberFormulaArg(4)},
},
}, 0)
assert.Equal(t, 3, len(result))
assert.Equal(t, "1", result[0].Value())
assert.Equal(t, "", result[1].Value())
assert.Equal(t, "3", result[2].Value())
}
func TestCalcLookupLinearSearch(t *testing.T) {
lookupValue := newStringFormulaArg("test")
lookupArray := formulaArg{
Type: ArgString,
Matrix: [][]formulaArg{
{newStringFormulaArg("test")},
},
}
matchMode := newNumberFormulaArg(0)
searchMode := newNumberFormulaArg(1)
idx, wasExact := lookupLinearSearch(false, lookupValue, lookupArray, matchMode, searchMode)
assert.Equal(t, 0, idx)
assert.True(t, wasExact)
}
func TestCalcMatchMatrix(t *testing.T) {
assert.Equal(t, formulaArg{Type: ArgNumber, Number: 2},
calcMatchMatrix(true, 1, &formulaCriteria{
Type: criteriaEq,
Condition: newStringFormulaArg("B"),
}, [][]formulaArg{
{newStringFormulaArg("A")},
{newStringFormulaArg("B")},
{newStringFormulaArg("C")},
}),
)
}
func TestCalcSORTBY(t *testing.T) {
cellData := [][]interface{}{
{"Name", "Region", "Sales", "Quarter"},
{"Alice", "North", 1500, 1},
{"Bob", "South", 2000, 2},
{"Alice", "North", 1200, 2},
{"Charlie", "East", 1800, 1},
{"Bob", "South", 2200, 1},
{"David", "West", 1000, 2},
{"Alice", "North", 1700, 3},
{"Test", "Mixed", "Text", 4},
{nil, "Empty", 500, 4},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, C2:C8))": "David,Alice,Alice,Alice,Charlie,Bob,Bob",
"TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, C2:C8, -1))": "Bob,Bob,Charlie,Alice,Alice,Alice,David",
"TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, B2:B8, 1, C2:C8, -1))": "Charlie,Alice,Alice,Alice,Bob,Bob,David",
"TEXTJOIN(\",\", TRUE, SORTBY(A2:A8, D2:D8, 1, B2:B8, 1, C2:C8, -1))": "Charlie,Alice,Bob,Alice,Bob,David,Alice",
"TEXTJOIN(\";\", TRUE, SORTBY(A2:C4, C2:C4))": "Alice;North;1200;Alice;North;1500;Bob;South;2000",
"TEXTJOIN(\",\", TRUE, SORTBY(C2:C8, A2:B8))": "1500,1200,1700,2000,2200,1800,1000",
"TEXTJOIN(\",\", TRUE, SORTBY(A2:A10, C2:C10))": "David,Alice,Alice,Alice,Charlie,Bob,Bob,Test",
"TEXTJOIN(\",\", TRUE, SORTBY(D2:D8, D2:D8))": "1,1,1,2,2,2,3",
"TEXTJOIN(\",\", TRUE, SORTBY(B2:B8, B2:B8))": "East,North,North,North,South,South,West",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
result, err := f.CalcCellValue("Sheet1", "E1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string][]string{
"SORTBY(A2:A7)": {"#VALUE!", "SORTBY requires at least 2 arguments"},
"SORTBY(A2:A7, C2:C7, 1, D2:D7, 1, B2:B7, 1, E2:E7, 1)": {"#VALUE!", "SORTBY takes at most 7 arguments, received 9"},
"SORTBY(A2:A7, C2:C7, 1, D2:D7)": {"#VALUE!", "SORTBY requires 2, 3, 5, or 7 arguments, received 4"},
"SORTBY(A2:A7, C2:C7, 1, D2:D7, 1, B2:B7)": {"#VALUE!", "SORTBY requires 2, 3, 5, or 7 arguments, received 6"},
"SORTBY(A2:A7, C2:C5)": {"#VALUE!", "by_array dimensions (4 rows) do not match array dimensions (6 rows)"},
"SORTBY(A2:A7, C2:C7, 2)": {"#VALUE!", "sort_order must be 1 or -1, received 2"},
"SORTBY(A2:A7, C2:C7, 0)": {"#VALUE!", "sort_order must be 1 or -1, received 0"},
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "F1", formula))
result, err := f.CalcCellValue("Sheet1", "F1")
assert.EqualError(t, err, expected[1], formula)
assert.Equal(t, expected[0], result, formula)
}
f = prepareCalcData([][]interface{}{
{"Name", "Score"},
{"Alice", 100},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A2, B2:B2))"))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "Alice", result, "Single row should work")
f = prepareCalcData([][]interface{}{
{"Name", "Score"},
{"Alice", 100},
{"Bob", 100},
{"Charlie", 100},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A4, B2:B4))"))
result, err = f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, "Alice,Bob,Charlie", result, "Equal values maintain original order")
f = prepareCalcData([][]interface{}{
{"Name", "Group", "Score"},
{"Charlie", "A", 90},
{"Alice", "A", 95},
{"Bob", "A", 85},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "TEXTJOIN(\",\", TRUE, SORTBY(A2:A4, B2:B4, 1, C2:C4, 1))"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "Bob,Charlie,Alice", result, "Second key determines order when first is equal")
f = prepareCalcData([][]interface{}{
{"Name", "Score"},
{"Alice", 100},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=SORTBY(A2:A2,{})"))
result, err = f.CalcCellValue("Sheet1", "C1")
assert.Error(t, err)
assert.Equal(t, formulaErrorVALUE, result)
f = prepareCalcData([][]interface{}{
{"Name", "Score"},
{"Alice", 100},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=SORTBY({},B2:B2)"))
result, err = f.CalcCellValue("Sheet1", "C1")
assert.Error(t, err)
assert.Equal(t, formulaErrorVALUE, result)
f = prepareCalcData([][]interface{}{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
})
assert.NoError(t, f.SetCellFormula("Sheet1", "D2", "=SORTBY(NA(),B1:B2)"))
result, err = f.CalcCellValue("Sheet1", "D2")
assert.Equal(t, formulaErrorNA, result)
assert.Equal(t, formulaErrorNA, err.Error())
assert.NoError(t, f.SetCellFormula("Sheet1", "D4", "=SORTBY(A1:A2,NA())"))
_, err = f.CalcCellValue("Sheet1", "D4")
assert.Equal(t, formulaErrorNA, result)
assert.Equal(t, formulaErrorNA, err.Error())
argsList := list.New()
argsList.PushBack(newStringFormulaArg("text"))
nextByArray, errArg := parseSortOrderArg(argsList.Front(), &sortbyKey{}, 0, 3)
assert.Equal(t, "strconv.ParseFloat: parsing \"text\": invalid syntax", errArg.Error)
assert.NotNil(t, nextByArray)
nextByArray, errArg = parseSortOrderArg(argsList.Front(), &sortbyKey{}, 0, 1)
assert.Nil(t, errArg)
assert.NotNil(t, nextByArray)
}
func TestCalcTrendGrowthMultipleRegressionPart2(t *testing.T) {
calcTrendGrowthMultipleRegressionPart2(true, false,
[][]float64{{1}, {2}, {3}},
[][]float64{},
[][]float64{},
[][]float64{{0}},
2.0, 0, 0, 3)
calcTrendGrowthMultipleRegressionPart2(true, false,
[][]float64{{1}, {2}, {3}},
[][]float64{{0}, {0}, {0}},
[][]float64{},
[][]float64{{0}},
2.0, 0, 1, 3)
}
func TestCalcTrendGrowthRegression(t *testing.T) {
mtx := [][]float64{}
calcTrendGrowthRegression(false, false, 0, 0, 0, 0, 0, mtx, mtx, mtx, mtx)
}
func TestCalcImplicitIntersect(t *testing.T) {
f := NewFile()
for cell, value := range map[string]interface{}{
"A1": -5, "A2": 10, "A3": -3, "A4": "text", "A5": 7, "D1": 1, "D2": 0, "D3": 1,
} {
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
}
for cell, expected := range map[string]string{
"B1": "5", "B2": "10", "B3": "3", "B5": "7",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ABS(A1:A5)"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
for cell, expected := range map[string]string{
"C1": "TRUE", "C2": "TRUE", "C3": "TRUE", "C4": "FALSE", "C5": "TRUE",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ISNUMBER(A1:A5)"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
for cell, expected := range map[string]string{
"E1": "yes", "E2": "no", "E3": "yes",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "IF(D1:D3,\"yes\",\"no\")"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "ABS(A1:A5)"))
_, err := f.CalcCellValue("Sheet1", "B10")
assert.NoError(t, err)
result, err := formulaCriteriaEval(newStringFormulaArg("text"), &formulaCriteria{
Type: criteriaRegexp,
Condition: newStringFormulaArg("text"),
})
assert.NoError(t, err)
assert.True(t, result)
fn := formulaFuncs{}
assert.Equal(t, ArgMatrix, fn.implicitIntersect(
newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}})).Type,
)
}
================================================
FILE: calcchain.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
)
// calcChainReader provides a function to get the pointer to the structure
// after deserialization of xl/calcChain.xml.
func (f *File) calcChainReader() (*xlsxCalcChain, error) {
if f.CalcChain == nil {
f.CalcChain = new(xlsxCalcChain)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
Decode(f.CalcChain); err != nil && err != io.EOF {
return f.CalcChain, err
}
}
return f.CalcChain, nil
}
// calcChainWriter provides a function to save xl/calcChain.xml after
// serialize structure.
func (f *File) calcChainWriter() {
if f.CalcChain != nil && f.CalcChain.C != nil {
output, _ := xml.Marshal(f.CalcChain)
f.saveFileList(defaultXMLPathCalcChain, output)
}
}
// deleteCalcChain provides a function to remove cell reference on the
// calculation chain.
func (f *File) deleteCalcChain(index int, cell string) error {
calc, err := f.calcChainReader()
if err != nil {
return err
}
if calc != nil {
calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool {
return (c.I != index || c.R != cell) && (c.I != index || cell != "") && (c.I != 0 || c.R != cell)
})
}
if len(calc.C) == 0 {
f.CalcChain = nil
f.Pkg.Delete(defaultXMLPathCalcChain)
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for k, v := range content.Overrides {
if v.PartName == "/xl/calcChain.xml" {
content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
}
}
}
return err
}
type xlsxCalcChainCollection []xlsxCalcChainC
// Filter provides a function to filter calculation chain.
func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC {
var results []xlsxCalcChainC
for _, v := range c {
if fn(v) {
results = append(results, v)
}
}
return results
}
// volatileDepsReader provides a function to get the pointer to the structure
// after deserialization of xl/volatileDependencies.xml.
func (f *File) volatileDepsReader() (*xlsxVolTypes, error) {
if f.VolatileDeps == nil {
volatileDeps, ok := f.Pkg.Load(defaultXMLPathVolatileDeps)
if !ok {
return f.VolatileDeps, nil
}
f.VolatileDeps = new(xlsxVolTypes)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(volatileDeps.([]byte)))).
Decode(f.VolatileDeps); err != nil && err != io.EOF {
return f.VolatileDeps, err
}
}
return f.VolatileDeps, nil
}
// volatileDepsWriter provides a function to save xl/volatileDependencies.xml
// after serialize structure.
func (f *File) volatileDepsWriter() {
if f.VolatileDeps != nil {
output, _ := xml.Marshal(f.VolatileDeps)
f.saveFileList(defaultXMLPathVolatileDeps, output)
}
}
// deleteVolTopicRef provides a function to remove cell reference on the
// volatile dependencies topic.
func (vt *xlsxVolTypes) deleteVolTopicRef(i1, i2, i3, i4 int) {
for i := range vt.VolType[i1].Main[i2].Tp[i3].Tr {
if i == i4 {
vt.VolType[i1].Main[i2].Tp[i3].Tr = append(vt.VolType[i1].Main[i2].Tp[i3].Tr[:i], vt.VolType[i1].Main[i2].Tp[i3].Tr[i+1:]...)
}
}
}
================================================
FILE: calcchain_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCalcChainReader(t *testing.T) {
f := NewFile()
// Test read calculation chain with unsupported charset
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
_, err := f.calcChainReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteCalcChain(t *testing.T) {
f := NewFile()
f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}}
f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{
PartName: "/xl/calcChain.xml",
})
assert.NoError(t, f.deleteCalcChain(1, "A1"))
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellFormula("Sheet1", "A1", ""), "XML syntax error on line 1: invalid UTF-8")
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
// Test delete calculation chain with unsupported charset calculation chain
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8")
// Test delete calculation chain with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
}
================================================
FILE: cell.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"fmt"
"math"
"os"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/xuri/efp"
)
// CellType is the type of cell value type.
type CellType byte
// Cell value types enumeration.
const (
CellTypeUnset CellType = iota
CellTypeBool
CellTypeDate
CellTypeError
CellTypeFormula
CellTypeInlineString
CellTypeNumber
CellTypeSharedString
)
const (
// STCellFormulaTypeArray defined the formula is an array formula.
STCellFormulaTypeArray = "array"
// STCellFormulaTypeDataTable defined the formula is a data table formula.
STCellFormulaTypeDataTable = "dataTable"
// STCellFormulaTypeNormal defined the formula is a regular cell formula.
STCellFormulaTypeNormal = "normal"
// STCellFormulaTypeShared defined the formula is part of a shared formula.
STCellFormulaTypeShared = "shared"
)
// cellTypes mapping the cell's data type and enumeration.
var cellTypes = map[string]CellType{
"b": CellTypeBool,
"d": CellTypeDate,
"n": CellTypeNumber,
"e": CellTypeError,
"s": CellTypeSharedString,
"str": CellTypeFormula,
"inlineStr": CellTypeInlineString,
}
// GetCellValue provides a function to get formatted value from cell by given
// worksheet name and cell reference in spreadsheet. The return value is
// converted to the 'string' data type. This function is concurrency safe. If
// the cell format can be applied to the value of a cell, the applied value
// will be returned, otherwise the original value will be returned. All cells'
// values will be the same in a merged range.
func (f *File) GetCellValue(sheet, cell string, opts ...Options) (string, error) {
return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
sst, err := f.sharedStringsReader()
if err != nil {
return "", true, err
}
val, err := c.getValueFrom(f, sst, f.getOptions(opts...).RawCellValue)
return val, true, err
})
}
// GetCellType provides a function to get the cell's data type by given
// worksheet name and cell reference in spreadsheet file.
func (f *File) GetCellType(sheet, cell string) (CellType, error) {
var (
err error
cellTypeStr string
cellType CellType
)
if cellTypeStr, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
return c.T, true, nil
}); err != nil {
return CellTypeUnset, err
}
cellType = cellTypes[cellTypeStr]
return cellType, err
}
// SetCellValue provides a function to set the value of a cell. This function
// is concurrency safe. The specified coordinates should not be in the first
// row of the table, a complex number can be set with string text. The
// following shows the supported data types:
//
// int
// int8
// int16
// int32
// int64
// uint
// uint8
// uint16
// uint32
// uint64
// float32
// float64
// string
// []byte
// time.Duration
// time.Time
// bool
// nil
//
// Note that default date format is m/d/yy h:mm of time.Time type value. You
// can set numbers format by the SetCellStyle function. If you need to set the
// specialized date in Excel like January 0, 1900 or February 29, 1900, these
// times can not representation in Go language time.Time data type. Please set
// the cell value as number 0 or 60, then create and bind the date-time number
// format style for the cell.
func (f *File) SetCellValue(sheet, cell string, value interface{}) error {
var err error
switch v := value.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
err = f.setCellIntFunc(sheet, cell, v)
case float32:
err = f.SetCellFloat(sheet, cell, float64(v), -1, 32)
case float64:
err = f.SetCellFloat(sheet, cell, v, -1, 64)
case string:
err = f.SetCellStr(sheet, cell, v)
case []byte:
err = f.SetCellStr(sheet, cell, string(v))
case time.Duration:
_, d := setCellDuration(v)
err = f.SetCellDefault(sheet, cell, d)
if err != nil {
return err
}
err = f.setDefaultTimeStyle(sheet, cell, getDurationNumFmt(v))
case time.Time:
err = f.setCellTimeFunc(sheet, cell, v)
case bool:
err = f.SetCellBool(sheet, cell, v)
case nil:
err = f.SetCellDefault(sheet, cell, "")
default:
err = f.SetCellStr(sheet, cell, fmt.Sprint(value))
}
return err
}
// String extracts characters from a string item.
func (x xlsxSI) String() string {
var value strings.Builder
if x.T != nil {
value.WriteString(x.T.Val)
}
for _, s := range x.R {
if s.T != nil {
value.WriteString(s.T.Val)
}
}
if value.Len() == 0 {
return ""
}
return bstrUnmarshal(value.String())
}
// hasValue determine if cell non-blank value.
func (c *xlsxC) hasValue() bool {
return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
}
// removeFormula delete formula for the cell.
func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) error {
f.clearCalcCache()
if c.F != nil && c.Vm == nil {
sheetID := f.getSheetID(sheet)
if err := f.deleteCalcChain(sheetID, c.R); err != nil {
return err
}
if c.F.T == STCellFormulaTypeShared && c.F.Ref != "" {
si := c.F.Si
for r, row := range ws.SheetData.Row {
for col, cell := range row.C {
if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si {
ws.SheetData.Row[r].C[col].F = nil
ws.formulaSI.Delete(si)
_ = f.deleteCalcChain(sheetID, cell.R)
}
}
}
}
c.F = nil
}
return nil
}
// setCellIntFunc is a wrapper of SetCellInt.
func (f *File) setCellIntFunc(sheet, cell string, value interface{}) error {
var err error
switch v := value.(type) {
case int:
err = f.SetCellInt(sheet, cell, int64(v))
case int8:
err = f.SetCellInt(sheet, cell, int64(v))
case int16:
err = f.SetCellInt(sheet, cell, int64(v))
case int32:
err = f.SetCellInt(sheet, cell, int64(v))
case int64:
err = f.SetCellInt(sheet, cell, v)
case uint:
err = f.SetCellUint(sheet, cell, uint64(v))
case uint8:
err = f.SetCellUint(sheet, cell, uint64(v))
case uint16:
err = f.SetCellUint(sheet, cell, uint64(v))
case uint32:
err = f.SetCellUint(sheet, cell, uint64(v))
case uint64:
err = f.SetCellUint(sheet, cell, v)
}
return err
}
// setCellTimeFunc provides a method to process time type of value for
// SetCellValue.
func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
ws.mu.Lock()
c.S = ws.prepareCellStyle(col, row, c.S)
ws.mu.Unlock()
var date1904, isNum bool
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if isNum, err = c.setCellTime(value, date1904); err != nil {
return err
}
if isNum {
_ = f.setDefaultTimeStyle(sheet, cell, getTimeNumFmt(value))
}
return err
}
// setCellTime prepares cell type and Excel time by given Go time.Time type
// timestamp.
func (c *xlsxC) setCellTime(value time.Time, date1904 bool) (isNum bool, err error) {
var excelTime float64
_, offset := value.In(value.Location()).Zone()
value = value.Add(time.Duration(offset) * time.Second)
if excelTime, err = timeToExcelTime(value, date1904); err != nil {
return
}
isNum = excelTime > 0
if isNum {
c.setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
} else {
c.setCellDefault(value.Format(time.RFC3339Nano))
}
return
}
// setCellDuration prepares cell type and value by given Go time.Duration type
// time duration.
func setCellDuration(value time.Duration) (t string, v string) {
v = strconv.FormatFloat(value.Seconds()/86400, 'f', -1, 32)
return
}
// SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell reference and cell value.
func (f *File) SetCellInt(sheet, cell string, value int64) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellInt(value)
c.IS = nil
return f.removeFormula(c, ws, sheet)
}
// setCellInt prepares cell type and string type cell value by a given integer.
func setCellInt(value int64) (t string, v string) {
v = strconv.FormatInt(value, 10)
return
}
// SetCellUint provides a function to set uint type value of a cell by given
// worksheet name, cell reference and cell value.
func (f *File) SetCellUint(sheet, cell string, value uint64) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellUint(value)
c.IS = nil
return f.removeFormula(c, ws, sheet)
}
// setCellUint prepares cell type and string type cell value by a given unsigned
// integer.
func setCellUint(value uint64) (t string, v string) {
v = strconv.FormatUint(value, 10)
return
}
// SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell reference and cell value.
func (f *File) SetCellBool(sheet, cell string, value bool) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellBool(value)
c.IS = nil
return f.removeFormula(c, ws, sheet)
}
// setCellBool prepares cell type and string type cell value by a given boolean
// value.
func setCellBool(value bool) (t string, v string) {
t = "b"
if value {
v = "1"
} else {
v = "0"
}
return
}
// SetCellFloat sets a floating point value into a cell. The precision
// parameter specifies how many places after the decimal will be shown
// while -1 is a special value that will use as many decimal places as
// necessary to represent the number. bitSize is 32 or 64 depending on if a
// float32 or float64 was originally used for the value. For Example:
//
// var x float32 = 1.325
// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error {
if math.IsNaN(value) || math.IsInf(value, 0) {
return f.SetCellStr(sheet, cell, fmt.Sprint(value))
}
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.setCellFloat(value, precision, bitSize)
return f.removeFormula(c, ws, sheet)
}
// setCellFloat prepares cell type and string type cell value by a given float
// value.
func (c *xlsxC) setCellFloat(value float64, precision, bitSize int) {
if math.IsNaN(value) || math.IsInf(value, 0) {
c.setInlineStr(fmt.Sprint(value))
return
}
c.T, c.V = "", strconv.FormatFloat(value, 'f', precision, bitSize)
c.IS = nil
}
// SetCellStr provides a function to set string type value of a cell. Total
// number of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet, cell, value string) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
if c.T, c.V, err = f.setCellString(value); err != nil {
return err
}
c.IS = nil
return f.removeFormula(c, ws, sheet)
}
// setCellString provides a function to set string type to shared string table.
func (f *File) setCellString(value string) (t, v string, err error) {
if countUTF16String(value) > TotalCellChars {
value = truncateUTF16Units(value, TotalCellChars)
}
t = "s"
var si int
if si, err = f.setSharedString(value); err != nil {
return
}
v = strconv.Itoa(si)
return
}
// sharedStringsLoader load shared string table from system temporary file to
// memory, and reset shared string table for reader.
func (f *File) sharedStringsLoader() (err error) {
f.mu.Lock()
defer f.mu.Unlock()
if path, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
f.Pkg.Store(defaultXMLPathSharedStrings, f.readBytes(defaultXMLPathSharedStrings))
f.tempFiles.Delete(defaultXMLPathSharedStrings)
if err = os.Remove(path.(string)); err != nil {
return
}
f.SharedStrings = nil
}
if f.sharedStringTemp != nil {
if err := f.sharedStringTemp.Close(); err != nil {
return err
}
f.tempFiles.Delete(defaultTempFileSST)
f.sharedStringItem, err = nil, os.Remove(f.sharedStringTemp.Name())
f.sharedStringTemp = nil
}
return
}
// setSharedString provides a function to add string to the share string table.
func (f *File) setSharedString(val string) (int, error) {
if err := f.sharedStringsLoader(); err != nil {
return 0, err
}
sst, err := f.sharedStringsReader()
if err != nil {
return 0, err
}
f.mu.Lock()
defer f.mu.Unlock()
if i, ok := f.sharedStringsMap[val]; ok {
return i, nil
}
sst.mu.Lock()
defer sst.mu.Unlock()
t := xlsxT{Val: val}
val, t.Space = trimCellValue(val, false)
sst.SI = append(sst.SI, xlsxSI{T: &t})
sst.Count = len(sst.SI)
sst.UniqueCount = sst.Count
f.sharedStringsMap[val] = sst.UniqueCount - 1
return sst.UniqueCount - 1, nil
}
// trimCellValue provides a function to set string type to cell.
func trimCellValue(value string, escape bool) (v string, ns xml.Attr) {
if countUTF16String(value) > TotalCellChars {
value = truncateUTF16Units(value, TotalCellChars)
}
if value != "" {
prefix, suffix := value[0], value[len(value)-1]
for _, ascii := range []byte{9, 10, 13, 32} {
if prefix == ascii || suffix == ascii {
ns = xml.Attr{
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
Value: "preserve",
}
break
}
}
if escape {
var buf strings.Builder
_ = xml.EscapeText(&buf, []byte(value))
value = strings.ReplaceAll(buf.String(), "
", "\n")
}
}
v = bstrMarshal(value)
return
}
// setCellValue set cell data type and value for (inline) rich string cell or
// formula cell.
func (c *xlsxC) setCellValue(val string) {
if c.F != nil {
c.setStr(val)
return
}
c.setInlineStr(val)
}
// setInlineStr set cell data type and value which containing an (inline) rich
// string.
func (c *xlsxC) setInlineStr(val string) {
c.T, c.V, c.IS = "inlineStr", "", &xlsxSI{T: &xlsxT{}}
c.IS.T.Val, c.IS.T.Space = trimCellValue(val, true)
}
// setStr set cell data type and value which containing a formula string.
func (c *xlsxC) setStr(val string) {
c.T, c.IS = "str", nil
c.V, c.XMLSpace = trimCellValue(val, false)
}
// getCellBool parse cell value which containing a boolean.
func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
if !raw {
if c.V == "1" {
return "TRUE", nil
}
if c.V == "0" {
return "FALSE", nil
}
}
return f.formattedValue(c, raw, CellTypeBool)
}
// setCellDefault prepares cell type and string type cell value by a given
// string.
func (c *xlsxC) setCellDefault(value string) {
if ok, _, _ := isNumeric(value); !ok {
if value != "" {
c.setInlineStr(value)
c.IS.T.Val = value
return
}
c.T, c.V, c.IS = value, value, nil
return
}
c.T, c.V = "", value
}
// getCellDate parse cell value which contains a date in the ISO 8601 format.
func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
if !raw {
layout := "20060102T150405.999"
if strings.HasSuffix(c.V, "Z") {
layout = "20060102T150405Z"
if strings.Contains(c.V, "-") {
layout = "2006-01-02T15:04:05Z"
}
} else if strings.Contains(c.V, "-") {
layout = "2006-01-02 15:04:05Z"
}
if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil {
excelTime, _ := timeToExcelTime(timestamp, false)
c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
}
}
return f.formattedValue(c, raw, CellTypeDate)
}
// getValueFrom return a value from a column/row cell, this function is
// intended to be used with for range on rows an argument with the spreadsheet
// opened file.
func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
switch c.T {
case "b":
return c.getCellBool(f, raw)
case "d":
return c.getCellDate(f, raw)
case "s":
if c.V != "" {
xlsxSI, _ := strconv.Atoi(strings.TrimSpace(c.V))
if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
return f.formattedValue(&xlsxC{S: c.S, V: f.getFromStringItem(xlsxSI)}, raw, CellTypeSharedString)
}
d.mu.Lock()
defer d.mu.Unlock()
if len(d.SI) > xlsxSI {
return f.formattedValue(&xlsxC{S: c.S, V: d.SI[xlsxSI].String()}, raw, CellTypeSharedString)
}
}
return f.formattedValue(c, raw, CellTypeSharedString)
case "str":
return c.V, nil
case "inlineStr":
if c.IS != nil {
return f.formattedValue(&xlsxC{S: c.S, V: c.IS.String()}, raw, CellTypeInlineString)
}
return f.formattedValue(c, raw, CellTypeInlineString)
default:
if isNum, precision, decimal := isNumeric(c.V); isNum && !raw {
if precision > 15 {
c.V = strconv.FormatFloat(decimal, 'G', 15, 64)
} else {
c.V = strconv.FormatFloat(decimal, 'f', -1, 64)
}
}
return f.formattedValue(c, raw, CellTypeNumber)
}
}
// SetCellDefault provides a function to set string type value of a cell as
// default format without escaping the cell.
func (f *File) SetCellDefault(sheet, cell, value string) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.setCellDefault(value)
return f.removeFormula(c, ws, sheet)
}
// GetCellFormula provides a function to get formula from cell by given
// worksheet name and cell reference in spreadsheet.
func (f *File) GetCellFormula(sheet, cell string) (string, error) {
return f.getCellFormula(sheet, cell, false)
}
// getCellFormula provides a function to get transformed formula from cell by
// given worksheet name and cell reference in spreadsheet.
func (f *File) getCellFormula(sheet, cell string, transformed bool) (string, error) {
return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
if transformed && !f.formulaChecked {
if err := f.setArrayFormulaCells(); err != nil {
return "", false, err
}
f.formulaChecked = true
}
if transformed && c.f != "" {
return c.f, true, nil
}
if c.F == nil {
return "", false, nil
}
if c.F.T == STCellFormulaTypeShared && c.F.Si != nil {
formula, err := getSharedFormula(x, *c.F.Si, c.R)
return formula, true, err
}
return c.F.Content, true, nil
})
}
// FormulaOpts can be passed to SetCellFormula to use other formula types.
type FormulaOpts struct {
Type *string // Formula type
Ref *string // Shared formula ref
}
// SetCellFormula provides a function to set formula on the cell is taken
// according to the given worksheet name and cell formula settings. The result
// of the formula cell can be calculated when the worksheet is opened by the
// Office Excel application or can be using the "CalcCellValue" function also
// can get the calculated cell value. If the Excel application doesn't
// calculate the formula automatically when the workbook has been opened,
// please call "UpdateLinkedValue" after setting the cell formula functions.
//
// Example 1, set normal formula "SUM(A1,B1)" for the cell "A3" on "Sheet1":
//
// err := f.SetCellFormula("Sheet1", "A3", "SUM(A1,B1)")
//
// Example 2, set one-dimensional vertical constant array (column array) formula
// "1,2,3" for the cell "A3" on "Sheet1":
//
// err := f.SetCellFormula("Sheet1", "A3", "{1;2;3}")
//
// Example 3, set one-dimensional horizontal constant array (row array)
// formula '"a","b","c"' for the cell "A3" on "Sheet1":
//
// err := f.SetCellFormula("Sheet1", "A3", "{\"a\",\"b\",\"c\"}")
//
// Example 4, set two-dimensional constant array formula '{1,2,"a","b"}' for
// the cell "A3" on "Sheet1":
//
// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3"
// err := f.SetCellFormula("Sheet1", "A3", "{1,2;\"a\",\"b\"}",
// excelize.FormulaOpts{Ref: &ref, Type: &formulaType})
//
// Example 5, set range array formula "A1:A2" for the cell "A3" on "Sheet1":
//
// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3"
// err := f.SetCellFormula("Sheet1", "A3", "A1:A2",
// excelize.FormulaOpts{Ref: &ref, Type: &formulaType})
//
// Example 6, set shared formula "A1+B1" for the cell "C1:C5"
// on "Sheet1", "C1" is the master cell:
//
// formulaType, ref := excelize.STCellFormulaTypeShared, "C1:C5"
// err := f.SetCellFormula("Sheet1", "C1", "A1+B1",
// excelize.FormulaOpts{Ref: &ref, Type: &formulaType})
//
// Example 7, set table formula "SUM(Table1[[A]:[B]])" for the cell "C2"
// on "Sheet1":
//
// package main
//
// import (
// "fmt"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} {
// if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil {
// fmt.Println(err)
// return
// }
// }
// if err := f.AddTable("Sheet1", &excelize.Table{
// Range: "A1:C2", Name: "Table1", StyleName: "TableStyleMedium2",
// }); err != nil {
// fmt.Println(err)
// return
// }
// formulaType := excelize.STCellFormulaTypeDataTable
// if err := f.SetCellFormula("Sheet1", "C2", "SUM(Table1[[A]:[B]])",
// excelize.FormulaOpts{Type: &formulaType}); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
c, _, _, err := ws.prepareCell(cell)
if err != nil {
return err
}
f.clearCalcCache()
if formula == "" {
ws.deleteSharedFormula(c)
c.F = nil
return f.deleteCalcChain(f.getSheetID(sheet), cell)
}
if c.F != nil {
c.F.Content = formula
} else {
c.F = &xlsxF{Content: formula}
}
for _, opt := range opts {
if opt.Type != nil {
if *opt.Type == STCellFormulaTypeDataTable {
return err
}
c.F.T = *opt.Type
if c.F.T == STCellFormulaTypeArray && opt.Ref != nil {
if err = ws.setArrayFormula(sheet, &xlsxF{Ref: *opt.Ref, Content: formula}, f.GetDefinedName()); err != nil {
return err
}
}
if c.F.T == STCellFormulaTypeShared {
ws.deleteSharedFormula(c)
if err = ws.setSharedFormula(cell, *opt.Ref); err != nil {
return err
}
}
}
if opt.Ref != nil {
c.F.Ref = *opt.Ref
}
}
c.T, c.IS = "str", nil
return err
}
// setArrayFormula transform the array formula in an array formula range to the
// normal formula and set cells in this range to the formula as the normal
// formula.
func (ws *xlsxWorksheet) setArrayFormula(sheet string, formula *xlsxF, definedNames []DefinedName) error {
if len(strings.Split(formula.Ref, ":")) < 2 {
return nil
}
coordinates, err := rangeRefToCoordinates(formula.Ref)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
tokens, arrayFormulaOperandTokens, err := getArrayFormulaTokens(sheet, formula.Content, definedNames)
if err != nil {
return err
}
topLeftCol, topLeftRow := coordinates[0], coordinates[1]
for c := coordinates[0]; c <= coordinates[2]; c++ {
for r := coordinates[1]; r <= coordinates[3]; r++ {
colOffset, rowOffset := c-topLeftCol, r-topLeftRow
for i, af := range arrayFormulaOperandTokens {
colNum, rowNum := af.topLeftCol+colOffset, af.topLeftRow+rowOffset
if colNum <= af.bottomRightCol && rowNum <= af.bottomRightRow {
arrayFormulaOperandTokens[i].targetCellRef, _ = CoordinatesToCellName(colNum, rowNum)
}
}
ws.prepareSheetXML(c, r)
if cell := &ws.SheetData.Row[r-1].C[c-1]; cell.f == "" {
cell.f = transformArrayFormula(tokens, arrayFormulaOperandTokens)
}
}
}
return err
}
// setArrayFormulaCells transform the array formula in all worksheets to the
// normal formula and set cells in the array formula reference range to the
// formula as the normal formula.
func (f *File) setArrayFormulaCells() error {
definedNames := f.GetDefinedName()
for _, sheetN := range f.GetSheetList() {
ws, err := f.workSheetReader(sheetN)
if err != nil {
if err.Error() == newNotWorksheetError(sheetN).Error() {
continue
}
return err
}
for _, row := range ws.SheetData.Row {
for _, cell := range row.C {
if cell.F != nil && cell.F.T == STCellFormulaTypeArray {
if err = ws.setArrayFormula(sheetN, cell.F, definedNames); err != nil {
return err
}
}
}
}
}
return nil
}
// setSharedFormula set shared formula for the cells.
func (ws *xlsxWorksheet) setSharedFormula(cell, ref string) error {
coordinates, err := rangeRefToCoordinates(ref)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
si := ws.countSharedFormula()
for col := coordinates[0]; col <= coordinates[2]; col++ {
for rol := coordinates[1]; rol <= coordinates[3]; rol++ {
ws.prepareSheetXML(col, rol)
c := &ws.SheetData.Row[rol-1].C[col-1]
if c.F == nil {
c.F = &xlsxF{}
}
c.F.T = STCellFormulaTypeShared
if c.R == cell {
if c.F.Ref != "" {
si = *c.F.Si
continue
}
}
c.F.Si = &si
}
}
return err
}
// countSharedFormula count shared formula in the given worksheet.
func (ws *xlsxWorksheet) countSharedFormula() (count int) {
for _, row := range ws.SheetData.Row {
for _, cell := range row.C {
if cell.F != nil && cell.F.Si != nil && *cell.F.Si+1 > count {
count = *cell.F.Si + 1
}
}
}
return
}
// deleteSharedFormula delete shared formula cell from worksheet shared formula
// index cache and remove all shared cells formula which refer to the cell which
// containing the formula.
func (ws *xlsxWorksheet) deleteSharedFormula(c *xlsxC) {
if c.F != nil && c.F.Si != nil && c.F.Ref != "" {
si := *c.F.Si
ws.formulaSI.Delete(si)
for r, row := range ws.SheetData.Row {
for c, cell := range row.C {
if cell.F != nil && cell.F.Si != nil && *cell.F.Si == si && cell.F.Ref == "" {
ws.SheetData.Row[r].C[c].F = nil
}
}
}
}
}
// GetCellHyperLink gets a cell hyperlink based on the given worksheet name and
// cell reference. If the cell has a hyperlink, it will return 'true' and
// the link address, otherwise it will return 'false' and an empty link
// address.
//
// For example, get a hyperlink to a 'H6' cell on a worksheet named 'Sheet1':
//
// link, target, err := f.GetCellHyperLink("Sheet1", "H6")
func (f *File) GetCellHyperLink(sheet, cell string) (bool, string, error) {
// Check for correct cell name
if _, _, err := SplitCellName(cell); err != nil {
return false, "", err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return false, "", err
}
if ws.Hyperlinks != nil {
for _, link := range ws.Hyperlinks.Hyperlink {
ok, err := f.checkCellInRangeRef(cell, link.Ref)
if err != nil {
return false, "", err
}
if link.Ref == cell || ok {
if link.RID != "" {
return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err
}
return true, link.Location, err
}
}
}
return false, "", err
}
// GetHyperLinkCells returns cell references which contain hyperlinks in a
// given worksheet name and link type. The optional parameter 'linkType' use for
// specific link type, the optional values are "External" for website links,
// "Location" for moving to one of cell in this workbook, "None" for no links.
// If linkType is empty, it will return all hyperlinks in the worksheet.
func (f *File) GetHyperLinkCells(sheet, linkType string) ([]string, error) {
var rangeRef []string
ws, err := f.workSheetReader(sheet)
if err != nil {
return rangeRef, err
}
if ws.Hyperlinks != nil {
for _, link := range ws.Hyperlinks.Hyperlink {
switch linkType {
case "External":
if link.RID != "" {
rangeRef = append(rangeRef, link.Ref)
}
case "Location":
if link.Location != "" {
rangeRef = append(rangeRef, link.Ref)
}
case "None":
return rangeRef, err
case "":
rangeRef = append(rangeRef, link.Ref)
default:
return rangeRef, newInvalidLinkTypeError(linkType)
}
}
}
return rangeRef, err
}
// HyperlinkOpts can be passed to SetCellHyperlink to set optional hyperlink
// attributes (e.g. display value)
type HyperlinkOpts struct {
Display *string
Tooltip *string
}
// removeHyperLink remove hyperlink for worksheet and delete relationships for
// the worksheet by given sheet name and cell reference. Note that if the cell
// in a range reference, the whole hyperlinks will be deleted.
func (f *File) removeHyperLink(ws *xlsxWorksheet, sheet, cell string) error {
for idx := 0; idx < len(ws.Hyperlinks.Hyperlink); idx++ {
link := ws.Hyperlinks.Hyperlink[idx]
ok, err := f.checkCellInRangeRef(cell, link.Ref)
if err != nil {
return err
}
if link.Ref == cell || ok {
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:idx], ws.Hyperlinks.Hyperlink[idx+1:]...)
idx--
f.deleteSheetRelationships(sheet, link.RID)
}
}
if len(ws.Hyperlinks.Hyperlink) == 0 {
ws.Hyperlinks = nil
}
return nil
}
// SetCellHyperLink provides a function to set cell hyperlink by given
// worksheet name and link URL address. LinkType defines three types of
// hyperlink "External" for website or "Location" for moving to one of cell in
// this workbook or "None" for remove hyperlink. Maximum limit hyperlinks in a
// worksheet is 65530. This function is only used to set the hyperlink of the
// cell and doesn't affect the value of the cell. If you need to set the value
// of the cell, please use the other functions such as `SetCellStyle` or
// `SetSheetRow`. The below is example for external link.
//
// display, tooltip := "https://github.com/xuri/excelize", "Excelize on GitHub"
// if err := f.SetCellHyperLink("Sheet1", "A3",
// display, "External", excelize.HyperlinkOpts{
// Display: &display,
// Tooltip: &tooltip,
// }); err != nil {
// fmt.Println(err)
// }
// // Set underline and font color style for the cell.
// style, err := f.NewStyle(&excelize.Style{
// Font: &excelize.Font{Color: "1265BE", Underline: "single"},
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "A3", "A3", style)
//
// This is another example for "Location":
//
// err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
func (f *File) SetCellHyperLink(sheet, cell, link, linkType string, opts ...HyperlinkOpts) error {
// Check for correct cell name
if _, _, err := SplitCellName(cell); err != nil {
return err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if cell, err = ws.mergeCellsParser(cell); err != nil {
return err
}
var linkData xlsxHyperlink
idx := -1
if ws.Hyperlinks == nil {
ws.Hyperlinks = new(xlsxHyperlinks)
}
for i, hyperlink := range ws.Hyperlinks.Hyperlink {
if hyperlink.Ref == cell {
idx = i
linkData = hyperlink
break
}
}
if len(ws.Hyperlinks.Hyperlink) > TotalSheetHyperlinks {
return ErrTotalSheetHyperlinks
}
switch linkType {
case "External":
sheetPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
rID := f.setRels(linkData.RID, sheetRels, SourceRelationshipHyperLink, link, linkType)
linkData = xlsxHyperlink{
Ref: cell,
}
linkData.RID = "rId" + strconv.Itoa(rID)
f.addSheetNameSpace(sheet, SourceRelationship)
case "Location":
linkData = xlsxHyperlink{
Ref: cell,
Location: link,
}
case "None":
return f.removeHyperLink(ws, sheet, cell)
default:
return newInvalidLinkTypeError(linkType)
}
for _, o := range opts {
if o.Display != nil {
linkData.Display = *o.Display
}
if o.Tooltip != nil {
linkData.Tooltip = *o.Tooltip
}
}
if idx == -1 {
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, linkData)
return err
}
ws.Hyperlinks.Hyperlink[idx] = linkData
return err
}
// getCellRichText returns rich text of cell by given string item.
func getCellRichText(si *xlsxSI) (runs []RichTextRun) {
if si.T != nil {
runs = append(runs, RichTextRun{Text: si.T.Val})
}
for _, v := range si.R {
run := RichTextRun{
Text: v.T.Val,
}
if v.RPr != nil {
run.Font = v.RPr.getFont()
}
runs = append(runs, run)
}
return
}
// GetCellRichText provides a function to get rich text of cell by given
// worksheet and cell reference.
func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return
}
c, _, _, err := ws.prepareCell(cell)
if err != nil {
return
}
if c.T == "inlineStr" && c.IS != nil {
runs = getCellRichText(c.IS)
return
}
if c.T != "s" || c.V == "" {
return
}
siIdx, err := strconv.Atoi(c.V)
if err != nil {
return
}
sst, err := f.sharedStringsReader()
if err != nil {
return
}
if len(sst.SI) <= siIdx || siIdx < 0 {
return
}
runs = getCellRichText(&sst.SI[siIdx])
return
}
// newRpr create run properties for the rich text by given font format.
func (fnt *Font) newRpr() *xlsxRPr {
font := fnt.newFont()
return &xlsxRPr{
RFont: font.Name,
Charset: font.Charset,
Family: font.Family,
B: font.B,
I: font.I,
Strike: font.Strike,
Outline: font.Outline,
Shadow: font.Shadow,
Condense: font.Condense,
Extend: font.Extend,
Color: font.Color,
Sz: font.Sz,
U: font.U,
VertAlign: font.VertAlign,
Scheme: font.Scheme,
}
}
// getFont create font format by given internal font properties.
func (rPr *xlsxRPr) getFont() *Font {
return extractFont(&xlsxFont{
Name: rPr.RFont,
Charset: rPr.Charset,
Family: rPr.Family,
B: rPr.B,
I: rPr.I,
Strike: rPr.Strike,
Outline: rPr.Outline,
Shadow: rPr.Shadow,
Condense: rPr.Condense,
Extend: rPr.Extend,
Color: rPr.Color,
Sz: rPr.Sz,
U: rPr.U,
VertAlign: rPr.VertAlign,
Scheme: rPr.Scheme,
})
}
// setRichText provides a function to set rich text of a cell.
func setRichText(runs []RichTextRun) ([]xlsxR, error) {
var (
textRuns []xlsxR
totalCellChars int
)
for _, textRun := range runs {
totalCellChars += countUTF16String(textRun.Text)
if totalCellChars > TotalCellChars {
return textRuns, ErrCellCharsLength
}
run := xlsxR{T: &xlsxT{}}
run.T.Val, run.T.Space = trimCellValue(textRun.Text, false)
fnt := textRun.Font
if fnt != nil {
run.RPr = fnt.newRpr()
}
textRuns = append(textRuns, run)
}
return textRuns, nil
}
// SetCellRichText provides a function to set cell with rich text by given
// worksheet name, cell reference and rich text runs. For example, set rich text
// on the A1 cell of the worksheet named Sheet1:
//
// package main
//
// import (
// "fmt"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// if err := f.SetRowHeight("Sheet1", 1, 35); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetColWidth("Sheet1", "A", "A", 44); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{
// {
// Text: "bold",
// Font: &excelize.Font{
// Bold: true,
// Color: "2354E8",
// Family: "Times New Roman",
// },
// },
// {
// Text: " and ",
// Font: &excelize.Font{
// Family: "Times New Roman",
// },
// },
// {
// Text: "italic ",
// Font: &excelize.Font{
// Bold: true,
// Color: "E83723",
// Italic: true,
// Family: "Times New Roman",
// },
// },
// {
// Text: "text with color and font-family,",
// Font: &excelize.Font{
// Bold: true,
// Color: "2354E8",
// Family: "Times New Roman",
// },
// },
// {
// Text: "\r\nlarge text with ",
// Font: &excelize.Font{
// Size: 14,
// Color: "AD23E8",
// },
// },
// {
// Text: "strike",
// Font: &excelize.Font{
// Color: "E89923",
// Strike: true,
// },
// },
// {
// Text: " superscript",
// Font: &excelize.Font{
// Color: "DBC21F",
// VertAlign: "superscript",
// },
// },
// {
// Text: " and ",
// Font: &excelize.Font{
// Size: 14,
// Color: "AD23E8",
// VertAlign: "baseline",
// },
// },
// {
// Text: "underline",
// Font: &excelize.Font{
// Color: "23E833",
// Underline: "single",
// },
// },
// {
// Text: " subscript.",
// Font: &excelize.Font{
// Color: "017505",
// VertAlign: "subscript",
// },
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
// style, err := f.NewStyle(&excelize.Style{
// Alignment: &excelize.Alignment{
// WrapText: true,
// },
// })
// if err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SetCellStyle("Sheet1", "A1", "A1", style); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
if err := f.sharedStringsLoader(); err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
si := xlsxSI{}
sst, err := f.sharedStringsReader()
if err != nil {
return err
}
if si.R, err = setRichText(runs); err != nil {
return err
}
f.clearCalcCache()
for idx, strItem := range sst.SI {
if reflect.DeepEqual(strItem, si) {
c.T, c.V = "s", strconv.Itoa(idx)
return err
}
}
sst.SI = append(sst.SI, si)
sst.Count++
sst.UniqueCount++
c.T, c.V = "s", strconv.Itoa(len(sst.SI)-1)
return err
}
// SetSheetRow writes an array to row by given worksheet name, starting
// cell reference and a pointer to array type 'slice'. This function is
// concurrency safe. For example, writes an array to row 6 start with the cell
// B6 on Sheet1:
//
// err := f.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2})
func (f *File) SetSheetRow(sheet, cell string, slice interface{}) error {
return f.setSheetCells(sheet, cell, slice, rows)
}
// SetSheetCol writes an array to column by given worksheet name, starting
// cell reference and a pointer to array type 'slice'. For example, writes an
// array to column B start with the cell B6 on Sheet1:
//
// err := f.SetSheetCol("Sheet1", "B6", &[]interface{}{"1", nil, 2})
func (f *File) SetSheetCol(sheet, cell string, slice interface{}) error {
return f.setSheetCells(sheet, cell, slice, columns)
}
// setSheetCells provides a function to set worksheet cells value.
func (f *File) setSheetCells(sheet, cell string, slice interface{}, dir adjustDirection) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
// Make sure 'slice' is a Ptr to Slice
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
return ErrParameterInvalid
}
v = v.Elem()
for i := 0; i < v.Len(); i++ {
var cell string
var err error
if dir == rows {
cell, err = CoordinatesToCellName(col+i, row)
} else {
cell, err = CoordinatesToCellName(col, row+i)
}
// Error should never happen here. But keep checking to early detect regressions
// if it will be introduced in the future.
if err != nil {
return err
}
if err := f.SetCellValue(sheet, cell, v.Index(i).Interface()); err != nil {
return err
}
}
return err
}
// getCellInfo does common preparation for all set cell value functions.
func (ws *xlsxWorksheet) prepareCell(cell string) (*xlsxC, int, int, error) {
var err error
cell, err = ws.mergeCellsParser(cell)
if err != nil {
return nil, 0, 0, err
}
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return nil, 0, 0, err
}
ws.prepareSheetXML(col, row)
return &ws.SheetData.Row[row-1].C[col-1], col, row, err
}
// getCellStringFunc does common value extraction workflow for all get cell
// value function. Passed function implements specific part of required
// logic.
func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return "", err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
cell, err = ws.mergeCellsParser(cell)
if err != nil {
return "", err
}
_, row, err := CellNameToCoordinates(cell)
if err != nil {
return "", err
}
lastRowNum := 0
if l := len(ws.SheetData.Row); l > 0 {
lastRowNum = ws.SheetData.Row[l-1].R
}
// keep in mind: row starts from 1
if row > lastRowNum {
return "", nil
}
idx, found := sort.Find(len(ws.SheetData.Row), func(i int) int {
if ws.SheetData.Row[i].R == row {
return 0
}
if ws.SheetData.Row[i].R > row {
return -1
}
return 1
})
if !found {
return "", nil
}
rowData := ws.SheetData.Row[idx]
for colIdx := range rowData.C {
colData := &rowData.C[colIdx]
if cell == colData.R {
val, ok, err := fn(ws, colData)
if err != nil {
return "", err
}
if ok {
return val, nil
}
break
}
}
return "", nil
}
// formattedValue provides a function to returns a value after formatted. If
// it is possible to apply a format to the cell value, it will do so, if not
// then an error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, error) {
if raw || c.S == 0 {
return c.V, nil
}
styleSheet, err := f.stylesReader()
if err != nil {
return c.V, err
}
if styleSheet.CellXfs == nil {
return c.V, err
}
if c.S >= len(styleSheet.CellXfs.Xf) || c.S < 0 {
return c.V, err
}
var numFmtID int
if styleSheet.CellXfs.Xf[c.S].NumFmtID != nil {
numFmtID = *styleSheet.CellXfs.Xf[c.S].NumFmtID
}
date1904 := false
wb, err := f.workbookReader()
if err != nil {
return c.V, err
}
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if fmtCode, ok := styleSheet.getCustomNumFmtCode(numFmtID); ok {
return format(c.V, fmtCode, date1904, cellType, f.options), err
}
if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok {
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
}
return c.V, err
}
// getCustomNumFmtCode provides a function to returns custom number format code.
func (ss *xlsxStyleSheet) getCustomNumFmtCode(numFmtID int) (string, bool) {
if ss.NumFmts == nil {
return "", false
}
for _, xlsxFmt := range ss.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtID {
if xlsxFmt.FormatCode16 != "" {
return xlsxFmt.FormatCode16, true
}
return xlsxFmt.FormatCode, true
}
}
return "", false
}
// prepareCellStyle provides a function to prepare style index of cell in
// worksheet by given column index and style index.
func (ws *xlsxWorksheet) prepareCellStyle(col, row, style int) int {
if style != 0 {
return style
}
if row <= len(ws.SheetData.Row) {
if styleID := ws.SheetData.Row[row-1].S; styleID != 0 {
return styleID
}
}
if ws.Cols != nil {
for _, c := range ws.Cols.Col {
if c.Min <= col && col <= c.Max && c.Style != 0 {
return c.Style
}
}
}
return style
}
// mergeCellsParser provides a function to check merged cells in worksheet by
// given cell reference.
func (ws *xlsxWorksheet) mergeCellsParser(cell string) (string, error) {
cell = strings.ToUpper(cell)
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return cell, err
}
if ws.MergeCells != nil {
for i := 0; i < len(ws.MergeCells.Cells); i++ {
if ws.MergeCells.Cells[i] == nil {
ws.MergeCells.Cells = append(ws.MergeCells.Cells[:i], ws.MergeCells.Cells[i+1:]...)
i--
continue
}
if ref := ws.MergeCells.Cells[i].Ref; len(ws.MergeCells.Cells[i].rect) == 0 && ref != "" {
if strings.Count(ref, ":") != 1 {
ref += ":" + ref
}
rect, err := rangeRefToCoordinates(ref)
if err != nil {
return cell, err
}
_ = sortCoordinates(rect)
ws.MergeCells.Cells[i].rect = rect
}
if cellInRange([]int{col, row}, ws.MergeCells.Cells[i].rect) {
cell = strings.Split(ws.MergeCells.Cells[i].Ref, ":")[0]
break
}
}
}
return cell, nil
}
// checkCellInRangeRef provides a function to determine if a given cell reference
// in a range.
func (f *File) checkCellInRangeRef(cell, rangeRef string) (bool, error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return false, err
}
if rng := strings.Split(rangeRef, ":"); len(rng) != 2 {
return false, err
}
coordinates, err := rangeRefToCoordinates(rangeRef)
if err != nil {
return false, err
}
return cellInRange([]int{col, row}, coordinates), err
}
// cellInRange provides a function to determine if a given range is within a
// range.
func cellInRange(cell, ref []int) bool {
return cell[0] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= ref[3]
}
// isOverlap find if the given two rectangles overlap or not.
func isOverlap(rect1, rect2 []int) bool {
return cellInRange([]int{rect1[0], rect1[1]}, rect2) ||
cellInRange([]int{rect1[2], rect1[1]}, rect2) ||
cellInRange([]int{rect1[0], rect1[3]}, rect2) ||
cellInRange([]int{rect1[2], rect1[3]}, rect2) ||
cellInRange([]int{rect2[0], rect2[1]}, rect1) ||
cellInRange([]int{rect2[2], rect2[1]}, rect1) ||
cellInRange([]int{rect2[0], rect2[3]}, rect1) ||
cellInRange([]int{rect2[2], rect2[3]}, rect1)
}
// convertSharedFormula creates a non shared formula from the shared formula
// counterpart by given cell reference which not containing the formula.
func (c *xlsxC) convertSharedFormula(cell string) (string, error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return "", err
}
sharedCol, sharedRow, err := CellNameToCoordinates(c.R)
if err != nil {
return "", err
}
dCol, dRow := col-sharedCol, row-sharedRow
ps := efp.ExcelParser()
tokens := ps.Parse(c.F.Content)
for i := range tokens {
token := tokens[i]
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
tokens[i].TValue = shiftCell(token.TValue, dCol, dRow)
}
}
return ps.Render(), nil
}
// getSharedFormula find a cell contains the same formula as another cell,
// the "shared" value can be used for the t attribute and the si attribute can
// be used to refer to the cell containing the formula. Two formulas are
// considered to be the same when their respective representations in
// R1C1-reference notation, are the same.
//
// Note that this function not validate ref tag to check the cell whether in
// allow range reference, and always return origin shared formula.
func getSharedFormula(ws *xlsxWorksheet, si int, cell string) (string, error) {
val, ok := ws.formulaSI.Load(si)
if ok {
return val.(*xlsxC).convertSharedFormula(cell)
}
for row := range ws.SheetData.Row {
r := &ws.SheetData.Row[row]
for column := range r.C {
c := &r.C[column]
if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si != nil && *c.F.Si == si {
ws.formulaSI.Store(si, c)
return c.convertSharedFormula(cell)
}
}
}
return "", nil
}
// shiftCell returns the cell shifted according to dCol and dRow taking into
// consideration absolute references with dollar sign ($)
func shiftCell(val string, dCol, dRow int) string {
parts := strings.Split(val, ":")
for j := 0; j < len(parts); j++ {
cell := parts[j]
trimmedCellName := strings.ReplaceAll(cell, "$", "")
c, r, err := CellNameToCoordinates(trimmedCellName)
if err == nil {
absCol := strings.Index(cell, "$") == 0
absRow := strings.LastIndex(cell, "$") > 0
if !absCol && !absRow {
parts[j], _ = CoordinatesToCellName(c+dCol, r+dRow)
}
if !absCol && absRow {
colName, _ := ColumnNumberToName(c + dCol)
parts[j] = colName + "$" + strconv.Itoa(r)
}
if absCol && !absRow {
colName, _ := ColumnNumberToName(c)
parts[j] = "$" + colName + strconv.Itoa(r+dRow)
}
continue
}
// Cell reference is a column name
c, err = ColumnNameToNumber(trimmedCellName)
if err == nil && !strings.HasPrefix(cell, "$") {
parts[j], _ = ColumnNumberToName(c + dCol)
continue
}
// Cell reference is a row number
r, err = strconv.Atoi(trimmedCellName)
if err == nil && !strings.HasPrefix(cell, "$") {
parts[j] = strconv.Itoa(r + dRow)
}
}
return strings.Join(parts, ":")
}
================================================
FILE: cell_test.go
================================================
package excelize
import (
"fmt"
_ "image/jpeg"
"math"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestConcurrency(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
wg := new(sync.WaitGroup)
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(val int, t *testing.T) {
// Concurrency set cell value
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val)))
// Concurrency get cell value
_, err := f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
assert.NoError(t, err)
// Concurrency set rows
assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{
" Hello",
[]byte("World"), 42, int8(1<<8/2 - 1), int16(1<<16/2 - 1), int32(1<<32/2 - 1),
int64(1<<32/2 - 1), float32(42.65418), -42.65418, float32(42), float64(42),
uint(1<<32 - 1), uint8(1<<8 - 1), uint16(1<<16 - 1), uint32(1<<32 - 1),
uint64(1<<32 - 1), true, complex64(5 + 10i),
}))
// Concurrency create style
style, err := f.NewStyle(&Style{Font: &Font{Color: "1265BE", Underline: "single"}})
assert.NoError(t, err)
// Concurrency set cell style
assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style))
// Concurrency get cell style
_, err = f.GetCellStyle("Sheet1", "A3")
assert.NoError(t, err)
// Concurrency add picture
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
&GraphicOptions{
OffsetX: 10,
OffsetY: 10,
Hyperlink: "https://github.com/xuri/excelize",
HyperlinkType: "External",
Positioning: "oneCell",
},
))
// Concurrency get cell picture
pics, err := f.GetPictures("Sheet1", "A1")
assert.Len(t, pics, 0)
assert.NoError(t, err)
// Concurrency iterate rows
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
for rows.Next() {
_, err := rows.Columns()
assert.NoError(t, err)
}
// Concurrency iterate columns
cols, err := f.Cols("Sheet1")
assert.NoError(t, err)
for cols.Next() {
_, err := cols.Rows()
assert.NoError(t, err)
}
// Concurrency set columns style
assert.NoError(t, f.SetColStyle("Sheet1", "C:E", style))
// Concurrency get columns style
styleID, err := f.GetColStyle("Sheet1", "D")
assert.NoError(t, err)
assert.Equal(t, style, styleID)
// Concurrency set columns width
assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 10))
// Concurrency get columns width
width, err := f.GetColWidth("Sheet1", "A")
assert.NoError(t, err)
assert.Equal(t, 10.0, width)
// Concurrency set columns visible
assert.NoError(t, f.SetColVisible("Sheet1", "A:B", true))
// Concurrency get columns visible
visible, err := f.GetColVisible("Sheet1", "A")
assert.NoError(t, err)
assert.Equal(t, true, visible)
// Concurrency add data validation
dv := NewDataValidation(true)
dv.Sqref = fmt.Sprintf("A%d:B%d", val, val)
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dv.SetInput(fmt.Sprintf("title:%d", val), strconv.Itoa(val))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
// Concurrency delete data validation with reference sequence
assert.NoError(t, f.DeleteDataValidation("Sheet1", dv.Sqref))
wg.Done()
}(i, t)
}
wg.Wait()
val, err := f.GetCellValue("Sheet1", "A1")
if err != nil {
t.Error(err)
}
assert.Equal(t, "1", val)
// Test the length of data validation
dataValidations, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dataValidations, 0)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestConcurrency.xlsx")))
assert.NoError(t, f.Close())
}
func TestCheckCellInRangeRef(t *testing.T) {
f := NewFile()
expectedTrueCellInRangeRefList := [][2]string{
{"c2", "A1:AAZ32"},
{"B9", "A1:B9"},
{"C2", "C2:C2"},
}
for _, expectedTrueCellInRangeRef := range expectedTrueCellInRangeRefList {
cell := expectedTrueCellInRangeRef[0]
reference := expectedTrueCellInRangeRef[1]
ok, err := f.checkCellInRangeRef(cell, reference)
assert.NoError(t, err)
assert.Truef(t, ok,
"Expected cell %v to be in range reference %v, got false\n", cell, reference)
}
expectedFalseCellInRangeRefList := [][2]string{
{"c2", "A4:AAZ32"},
{"C4", "D6:A1"}, // weird case, but you never know
{"AEF42", "BZ40:AEF41"},
}
for _, expectedFalseCellInRangeRef := range expectedFalseCellInRangeRefList {
cell := expectedFalseCellInRangeRef[0]
reference := expectedFalseCellInRangeRef[1]
ok, err := f.checkCellInRangeRef(cell, reference)
assert.NoError(t, err)
assert.Falsef(t, ok,
"Expected cell %v not to be inside of range reference %v, but got true\n", cell, reference)
}
ok, err := f.checkCellInRangeRef("A1", "A:B")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
assert.False(t, ok)
ok, err = f.checkCellInRangeRef("AA0", "Z0:AB1")
assert.Equal(t, newCellNameToCoordinatesError("AA0", newInvalidCellNameError("AA0")), err)
assert.False(t, ok)
}
func TestSetCellFloat(t *testing.T) {
sheet := "Sheet1"
t.Run("with no decimal", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.0, -1, 64))
assert.NoError(t, f.SetCellFloat(sheet, "A2", 123.0, 1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123", val, "A1 should be 123")
val, err = f.GetCellValue(sheet, "A2")
assert.NoError(t, err)
assert.Equal(t, "123", val, "A2 should be 123")
})
t.Run("with a decimal and precision limit", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, 1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123.4", val, "A1 should be 123.4")
})
t.Run("with a decimal and no limit", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, -1, 64))
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Equal(t, "123.42", val, "A1 should be 123.42")
})
f := NewFile()
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellFloat(sheet, "A", 123.42, -1, 64))
// Test set cell float data type value with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetCellFloat("Sheet:1", "A1", 123.42, -1, 64))
}
func TestSetCellUint(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint8(math.MaxUint8)))
result, err := f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "255", result)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint16)))
result, err = f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "65535", result)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint32)))
result, err = f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "4294967295", result)
assert.NoError(t, err)
// Test uint cell value not exists worksheet
assert.EqualError(t, f.SetCellUint("SheetN", "A1", 1), "sheet SheetN does not exist")
// Test uint cell value with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellUint("Sheet1", "A", 1))
}
func TestSetCellValuesMultiByte(t *testing.T) {
f := NewFile()
row := []interface{}{
// Test set cell value with multi byte characters value
strings.Repeat("\u4E00", TotalCellChars+1),
// Test set cell value with XML escape characters
strings.Repeat("<>", TotalCellChars/2),
strings.Repeat(">", TotalCellChars-1),
strings.Repeat(">", TotalCellChars+1),
}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &row))
// Test set cell value with XML escape characters in stream writer
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
streamWriter, err := f.NewStreamWriter("Sheet2")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A1", row))
assert.NoError(t, streamWriter.Flush())
for _, sheetName := range []string{"Sheet1", "Sheet2"} {
for cell, expected := range map[string]int{
"A1": TotalCellChars,
"B1": TotalCellChars - 1,
"C1": TotalCellChars - 1,
"D1": TotalCellChars,
} {
result, err := f.GetCellValue(sheetName, cell)
assert.NoError(t, err)
assert.Len(t, []rune(result), expected)
}
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellValuesMultiByte.xlsx")))
}
func TestSetCellValue(t *testing.T) {
f := NewFile()
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Now().UTC()))
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Duration(1e13)))
// Test set cell value with column and row style inherit
style1, err := f.NewStyle(&Style{NumFmt: 2})
assert.NoError(t, err)
style2, err := f.NewStyle(&Style{NumFmt: 9})
assert.NoError(t, err)
assert.NoError(t, f.SetColStyle("Sheet1", "B", style1))
assert.NoError(t, f.SetRowStyle("Sheet1", 1, 1, style2))
assert.NoError(t, f.SetCellValue("Sheet1", "B1", 0.5))
assert.NoError(t, f.SetCellValue("Sheet1", "B2", 0.5))
B1, err := f.GetCellValue("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "50%", B1)
B2, err := f.GetCellValue("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "0.50", B2)
// Test set cell value with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetCellValue("Sheet:1", "A1", "A1"))
// Test set cell value with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8")
// Test set cell value with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", time.Now().UTC()), "XML syntax error on line 1: invalid UTF-8")
// Test set cell value with the shared string table's count not equal with unique count
f = NewFile()
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, []byte(fmt.Sprintf(`aa`, NameSpaceSpreadSheet.Value)))
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
SheetData: xlsxSheetData{Row: []xlsxRow{
{R: 1, C: []xlsxC{{R: "A1", T: "str", V: "1"}}},
}},
})
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "b"))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "b", val)
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "b"))
val, err = f.GetCellValue("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "b", val)
f = NewFile()
// Test set cell value with an IEEE 754 "not-a-number" value or infinity
for num, expected := range map[float64]string{
math.NaN(): "NaN",
math.Inf(0): "+Inf",
math.Inf(-1): "-Inf",
} {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", num))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
// Test set cell value with time duration
for val, expected := range map[time.Duration]string{
time.Hour*21 + time.Minute*51 + time.Second*44: "21:51:44",
time.Hour*21 + time.Minute*50: "21:50",
time.Hour*24 + time.Minute*51 + time.Second*44: "24:51:44",
time.Hour*24 + time.Minute*50: "24:50:00",
} {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
// Test set cell value with time
for val, expected := range map[time.Time]string{
time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC): "Oct-24",
time.Date(2024, time.October, 10, 0, 0, 0, 0, time.UTC): "10-10-24",
time.Date(2024, time.October, 10, 12, 0, 0, 0, time.UTC): "10/10/24 12:00",
} {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
}
func TestSetCellValues(t *testing.T) {
f := NewFile()
err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err)
v, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, v, "12-31-10")
// Test date value lower than min date supported by Excel
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err)
v, err = f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, v, "1600-12-31T00:00:00Z")
}
func TestSetCellBool(t *testing.T) {
f := NewFile()
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellBool("Sheet1", "A", true))
// Test set cell boolean data type value with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetCellBool("Sheet:1", "A1", true))
}
func TestSetCellTime(t *testing.T) {
date, err := time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z")
assert.NoError(t, err)
for location, expected := range map[string]string{
"America/New_York": "40127.75",
"Asia/Shanghai": "40128.291666666664",
"Europe/London": "40127.958333333336",
"UTC": "40127.958333333336",
} {
timezone, err := time.LoadLocation(location)
assert.NoError(t, err)
c := &xlsxC{}
isNum, err := c.setCellTime(date.In(timezone), false)
assert.NoError(t, err)
assert.Equal(t, true, isNum)
assert.Equal(t, expected, c.V)
}
}
func TestGetCellValue(t *testing.T) {
// Test get cell value without r attribute of the row
f := NewFile()
sheetData := `%s`
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
f.checked = sync.Map{}
cells := []string{"A3", "A4", "B4", "A7", "B7"}
rows, err := f.GetRows("Sheet1")
assert.Equal(t, [][]string{nil, nil, {"A3"}, {"A4", "B4"}, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
assert.NoError(t, err)
for _, cell := range cells {
value, err := f.GetCellValue("Sheet1", cell)
assert.Equal(t, cell, value)
assert.NoError(t, err)
}
cols, err := f.GetCols("Sheet1")
assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
f.checked = sync.Map{}
cell, err := f.GetCellValue("Sheet1", "A2")
assert.Equal(t, "A2", cell)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
f.checked = sync.Map{}
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1
B1
`)))
f.checked = sync.Map{}
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
f.checked = sync.Map{}
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `H6r0A6F4
A6B6C6
100B3
`)))
f.checked = sync.Map{}
cell, err = f.GetCellValue("Sheet1", "H6")
assert.Equal(t, "H6", cell)
assert.NoError(t, err)
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{
{"A6", "B6", "C6"},
nil,
{"100", "B3"},
{"", "", "", "", "", "F4"},
nil,
{"", "", "", "", "", "", "", "H6"},
}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1
|
A3
`)))
f.checked = sync.Map{}
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A1"}, nil, {"A3"}}, rows)
assert.NoError(t, err)
cell, err = f.GetCellValue("Sheet1", "A3")
assert.Equal(t, "A3", cell)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `
2422.3000000000002
2422.3000000000002
12.4
964
1101.5999999999999
275.39999999999998
68.900000000000006
44385.208333333336
5.0999999999999996
5.1100000000000003
5.0999999999999996
5.1109999999999998
5.1111000000000004
2422.012345678
2422.0123456789
12.012345678901
964
1101.5999999999999
275.39999999999998
68.900000000000006
8.8880000000000001E-2
4.0000000000000003e-5
2422.3000000000002
1101.5999999999999
275.39999999999998
68.900000000000006
1.1000000000000001
1234567890123_4
123456789_0123_4
+0.0000000000000000002399999999999992E-4
7.2399999999999992E-2
20200208T080910.123
20200208T080910,123
20221022T150529Z
2022-10-22T15:05:29Z
2020-07-10 15:00:00.000
`)))
f.checked = sync.Map{}
rows, err = f.GetCols("Sheet1")
assert.Equal(t, []string{
"2422.3",
"2422.3",
"12.4",
"964",
"1101.6",
"275.4",
"68.9",
"44385.2083333333",
"5.1",
"5.11",
"5.1",
"5.111",
"5.1111",
"2422.012345678",
"2422.0123456789",
"12.012345678901",
"964",
"1101.6",
"275.4",
"68.9",
"0.08888",
"0.00004",
"2422.3",
"1101.6",
"275.4",
"68.9",
"1.1",
"1234567890123_4",
"123456789_0123_4",
"2.39999999999999E-23",
"0.0724",
"43869.3397004977",
"43869.3397004977",
"44856.6288078704",
"44856.6288078704",
"2020-07-10 15:00:00.000",
}, rows[0])
assert.NoError(t, err)
// Test get cell value with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, value := f.GetCellValue("Sheet1", "A1")
assert.EqualError(t, value, "XML syntax error on line 1: invalid UTF-8")
// Test get cell value with invalid sheet name
_, err = f.GetCellValue("Sheet:1", "A1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
func TestGetCellType(t *testing.T) {
f := NewFile()
cellType, err := f.GetCellType("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, CellTypeUnset, cellType)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
cellType, err = f.GetCellType("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, CellTypeSharedString, cellType)
_, err = f.GetCellType("Sheet1", "A")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
// Test get cell type with invalid sheet name
_, err = f.GetCellType("Sheet:1", "A1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
func TestGetValueFrom(t *testing.T) {
f := NewFile()
c := xlsxC{T: "s"}
sst, err := f.sharedStringsReader()
assert.NoError(t, err)
value, err := c.getValueFrom(f, sst, false)
assert.NoError(t, err)
assert.Empty(t, value)
c = xlsxC{T: "s", V: " 1 "}
value, err = c.getValueFrom(f, &xlsxSST{Count: 1, SI: []xlsxSI{{}, {T: &xlsxT{Val: "s"}}}}, false)
assert.NoError(t, err)
assert.Equal(t, "s", value)
}
func TestGetCellFormula(t *testing.T) {
// Test get cell formula on not exist worksheet
f := NewFile()
_, err := f.GetCellFormula("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get cell formula with invalid sheet name
_, err = f.GetCellFormula("Sheet:1", "A1")
assert.Equal(t, ErrSheetNameInvalid, err)
// Test get cell formula on no formula cell
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
// Test get cell shared formula
f = NewFile()
sheetData := `12*A1
2%s
3
4
5
6
7
`
for sharedFormula, expected := range map[string]string{
`2*A2`: `2*A3`,
`2*A1A`: `2*A1A`,
`2*$A$2+LEN("")`: `2*$A$2+LEN("")`,
`SUMIF(A:A,$B11, 5:5)`: `SUMIF(A:A,$B12,6:6)`,
`SUMIF(A:A,B$11, 5:5)`: `SUMIF(A:A,B$11,6:6)`,
} {
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, sharedFormula)))
formula, err := f.GetCellFormula("Sheet1", "B3")
assert.NoError(t, err)
assert.Equal(t, expected, formula)
// Test get shared formula form cache
formula, err = f.GetCellFormula("Sheet1", "B3")
assert.NoError(t, err)
assert.Equal(t, expected, formula)
}
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`
`))
formula, err := f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Empty(t, formula)
// Test get array formula with invalid cell range reference
f = NewFile()
assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line}))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaType, ref := STCellFormulaTypeArray, "B1:B2"
assert.NoError(t, f.SetCellFormula("Sheet2", "B1", "A1:B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
ws, ok := f.Sheet.Load("xl/worksheets/sheet3.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[1].F.Ref = ":"
_, err = f.getCellFormula("Sheet2", "A1", true)
assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err)
// Test set formula for the cells in array formula range with unsupported charset
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.setArrayFormulaCells(), "XML syntax error on line 1: invalid UTF-8")
// Test get shared formula after updated refer cell formula, the shared
// formula cell reference range covered the previous.
f = NewFile()
formulaType, ref = STCellFormulaTypeShared, "C2:C6"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "A2+B2", formula)
formula, err = f.GetCellFormula("Sheet1", "C6")
assert.NoError(t, err)
assert.Equal(t, "A6+B6", formula)
formulaType, ref = STCellFormulaTypeShared, "C2:C8"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "A2*B2", formula)
formula, err = f.GetCellFormula("Sheet1", "C8")
assert.NoError(t, err)
assert.Equal(t, "A8*B8", formula)
assert.NoError(t, f.Close())
// Test get shared formula after updated refer cell formula, the shared
// formula cell reference range not over the previous.
f = NewFile()
formulaType, ref = STCellFormulaTypeShared, "C2:C6"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "A2+B2", formula)
formula, err = f.GetCellFormula("Sheet1", "C6")
assert.NoError(t, err)
assert.Equal(t, "A6+B6", formula)
formulaType, ref = STCellFormulaTypeShared, "C2:C4"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "A2*B2", formula)
formula, err = f.GetCellFormula("Sheet1", "C6")
assert.NoError(t, err)
assert.Empty(t, formula)
// Test get shared formula after remove refer cell formula
f = NewFile()
formulaType, ref = STCellFormulaTypeShared, "C2:C6"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2+B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", ""))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Empty(t, formula)
formula, err = f.GetCellFormula("Sheet1", "C6")
assert.NoError(t, err)
assert.Empty(t, formula)
formulaType, ref = STCellFormulaTypeShared, "C2:C8"
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "A2*B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
formula, err = f.GetCellFormula("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, "A2*B2", formula)
formula, err = f.GetCellFormula("Sheet1", "C8")
assert.NoError(t, err)
assert.Equal(t, "A8*B8", formula)
assert.NoError(t, f.Close())
}
func TestConvertSharedFormula(t *testing.T) {
c := xlsxC{R: "A"}
_, err := c.convertSharedFormula("A")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
_, err = c.convertSharedFormula("A1")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
}
func ExampleFile_SetCellFloat() {
f := NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
x := 3.14159265
if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil {
fmt.Println(err)
}
val, err := f.GetCellValue("Sheet1", "A1")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(val)
// Output: 3.14
}
func BenchmarkSetCellValue(b *testing.B) {
values := []string{"First", "Second", "Third", "Fourth", "Fifth", "Sixth"}
cols := []string{"A", "B", "C", "D", "E", "F"}
f := NewFile()
b.ResetTimer()
for i := 1; i <= b.N; i++ {
for j := 0; j < len(values); j++ {
if err := f.SetCellValue("Sheet1", cols[j]+strconv.Itoa(i), values[j]); err != nil {
b.Error(err)
}
}
}
}
func TestOverflowNumericCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "OverflowNumericCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
// GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648"
assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225")
assert.NoError(t, f.Close())
}
func TestSetCellFormula(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SetCellFormula("Sheet1", "B19", "SUM(Sheet2!D2,Sheet2!D11)"))
assert.NoError(t, f.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)"))
// Test set cell formula with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetCellFormula("Sheet:1", "A1", "SUM(1,2)"))
// Test set cell formula with illegal rows number
assert.Equal(t, newCellNameToCoordinatesError("C", newInvalidCellNameError("C")), f.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx")))
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "CalcChain.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
// Test remove cell formula
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", ""))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula2.xlsx")))
// Test remove all cell formula
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", ""))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula3.xlsx")))
assert.NoError(t, f.Close())
// Test set shared formula for the cells
f = NewFile()
for r := 1; r <= 5; r++ {
assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", r), &[]interface{}{r, r + 1}))
}
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
sharedFormulaSpreadsheet := filepath.Join("test", "TestSetCellFormula4.xlsx")
assert.NoError(t, f.SaveAs(sharedFormulaSpreadsheet))
f, err = OpenFile(sharedFormulaSpreadsheet)
assert.NoError(t, err)
ref = "D1:D5"
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}))
ref = ""
assert.Equal(t, ErrParameterInvalid, f.SetCellFormula("Sheet1", "D1", "A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula5.xlsx")))
// Test set table formula for the cells
f = NewFile()
for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} {
assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row))
}
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:C2", Name: "Table1", StyleName: "TableStyleMedium2"}))
formulaType = STCellFormulaTypeDataTable
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx")))
// Test set array formula with invalid cell range reference
formulaType, ref = STCellFormulaTypeArray, ":"
assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &ref, Type: &formulaType}))
// Test set array formula with invalid cell reference
formulaType, ref = STCellFormulaTypeArray, "A1:A2"
assert.Equal(t, ErrColumnNumber, f.SetCellFormula("Sheet1", "A1", "SUM(XFE1:XFE2)", FormulaOpts{Ref: &ref, Type: &formulaType}))
}
func TestGetCellRichText(t *testing.T) {
f, theme := NewFile(), 1
runsSource := []RichTextRun{
{
Text: "a\n",
},
{
Text: "b",
Font: &Font{
Underline: "single",
Color: "ff0000",
ColorTheme: &theme,
ColorTint: 0.5,
Bold: true,
Italic: true,
Family: "Times New Roman",
Size: 100,
Strike: true,
},
},
}
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", runsSource))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", false))
runs, err := f.GetCellRichText("Sheet1", "A2")
assert.NoError(t, err)
assert.Equal(t, []RichTextRun(nil), runs)
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, runsSource[0].Text, runs[0].Text)
assert.Nil(t, runs[0].Font)
assert.NotNil(t, runs[1].Font)
runsSource[1].Font.Color = strings.ToUpper(runsSource[1].Font.Color)
assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font")
// Test get cell rich text with inlineStr
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{
T: "inlineStr",
IS: &xlsxSI{
T: &xlsxT{Val: "A"},
R: []xlsxR{{T: &xlsxT{Val: "1"}}},
},
}
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, []RichTextRun{{Text: "A"}, {Text: "1"}}, runs)
// Test get cell rich text when string item index overflow
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{V: "2", IS: &xlsxSI{}}
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 0, len(runs))
// Test get cell rich text when string item index is negative
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{T: "s", V: "-1", IS: &xlsxSI{}}
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 0, len(runs))
// Test get cell rich text when string item index is invalid
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{T: "s", V: "A", IS: &xlsxSI{}}
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
assert.Equal(t, 0, len(runs))
// Test get cell rich text on invalid string item index
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{V: "x"}
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 0, len(runs))
// Test set cell rich text on not exists worksheet
_, err = f.GetCellRichText("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test set cell rich text with illegal cell reference
_, err = f.GetCellRichText("Sheet1", "A")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
// Test set rich text color theme without tint
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTheme: &theme}}}))
// Test set rich text color tint without theme
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTint: 0.5}}}))
// Test set cell rich text with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", runsSource), "XML syntax error on line 1: invalid UTF-8")
// Test get cell rich text with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.GetCellRichText("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get cell rich text with invalid sheet name
_, err = f.GetCellRichText("Sheet:1", "A1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
func TestSetCellRichText(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
richTextRun := []RichTextRun{
{
Text: "bold",
Font: &Font{
Bold: true,
Color: "2354E8",
ColorIndexed: 0,
Family: "Times New Roman",
},
},
{
Text: " and ",
Font: &Font{
Family: "Times New Roman",
},
},
{
Text: "italic ",
Font: &Font{
Bold: true,
Color: "E83723",
Italic: true,
Family: "Times New Roman",
},
},
{
Text: "text with color and font-family, ",
Font: &Font{
Bold: true,
Color: "2354E8",
Family: "Times New Roman",
},
},
{
Text: "\r\nlarge text with ",
Font: &Font{
Size: 14,
Color: "AD23E8",
},
},
{
Text: "strike",
Font: &Font{
Color: "E89923",
Strike: true,
},
},
{
Text: " superscript",
Font: &Font{
Color: "DBC21F",
VertAlign: "superscript",
},
},
{
Text: " and ",
Font: &Font{
Size: 14,
Color: "AD23E8",
VertAlign: "baseline",
},
},
{
Text: "underline",
Font: &Font{
Color: "23E833",
Underline: "single",
},
},
{
Text: " subscript.",
Font: &Font{
Color: "017505",
VertAlign: "subscript",
},
},
}
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", richTextRun))
assert.NoError(t, f.SetCellRichText("Sheet1", "A2", richTextRun))
assert.NoError(t, f.SetCellRichText("Sheet1", "A3", []RichTextRun{{Text: strings.Repeat("\u4e00", TotalCellChars)}}))
style, err := f.NewStyle(&Style{
Alignment: &Alignment{
WrapText: true,
Vertical: "center",
},
})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "A"))
runs, err := f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, richTextRun, runs)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx")))
// Test set cell rich text on not exists worksheet
assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN does not exist")
// Test set cell rich text with invalid sheet name
assert.EqualError(t, f.SetCellRichText("Sheet:1", "A1", richTextRun), ErrSheetNameInvalid.Error())
// Test set cell rich text with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellRichText("Sheet1", "A", richTextRun))
richTextRun = []RichTextRun{{Text: strings.Repeat("s", TotalCellChars+1)}}
// Test set cell rich text with characters over the maximum limit
assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", richTextRun), ErrCellCharsLength.Error())
}
func TestFormattedValue(t *testing.T) {
f := NewFile()
result, err := f.formattedValue(&xlsxC{S: 0, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// S is too large
result, err = f.formattedValue(&xlsxC{S: 15, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// S is too small
result, err = f.formattedValue(&xlsxC{S: -15, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
customNumFmt := "[$-409]MM/DD/YYYY"
_, err = f.NewStyle(&Style{
CustomNumFmt: &customNumFmt,
})
assert.NoError(t, err)
result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "03/04/2019", result)
// Test format value with no built-in number format ID
numFmtID := 5
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
result, err = f.formattedValue(&xlsxC{S: 2, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// Test format value with invalid number format ID
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: nil,
})
result, err = f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// Test format value with empty number format
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
result, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// Test format numeric value with shared string data type
f.Styles.NumFmts, numFmtID = nil, 11
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
result, err = f.formattedValue(&xlsxC{S: 5, V: "43528"}, false, CellTypeSharedString)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// Test format decimal value with build-in number format ID
styleID, err := f.NewStyle(&Style{
NumFmt: 1,
})
assert.NoError(t, err)
result, err = f.formattedValue(&xlsxC{S: styleID, V: "310.56"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "311", result)
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber, nil))
// Test format value with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test format value with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber, nil))
}
func TestFormattedValueNilXfs(t *testing.T) {
// Set the CellXfs to nil and verify that the formattedValue function does not crash
f := NewFile()
f.Styles.CellXfs = nil
result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilNumFmts(t *testing.T) {
// Set the NumFmts value to nil and verify that the formattedValue function does not crash
f := NewFile()
f.Styles.NumFmts = nil
result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilWorkbook(t *testing.T) {
// Set the Workbook value to nil and verify that the formattedValue function does not crash
f := NewFile()
f.WorkBook = nil
result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilWorkbookPr(t *testing.T) {
// Set the WorkBook.WorkbookPr value to nil and verify that the formattedValue function does not
// crash.
f := NewFile()
f.WorkBook.WorkbookPr = nil
result, err := f.formattedValue(&xlsxC{S: 3, V: "43528"}, false, CellTypeNumber)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestGetCustomNumFmtCode(t *testing.T) {
expected := "[$-ja-JP-x-gannen,80]ggge\"年\"m\"月\"d\"日\";@"
styleSheet := &xlsxStyleSheet{NumFmts: &xlsxNumFmts{NumFmt: []*xlsxNumFmt{
{NumFmtID: 164, FormatCode16: expected},
}}}
numFmtCode, ok := styleSheet.getCustomNumFmtCode(164)
assert.Equal(t, expected, numFmtCode)
assert.True(t, ok)
}
func TestSharedStringsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
tempFile, ok := f.tempFiles.Load(defaultXMLPathSharedStrings)
assert.True(t, ok)
f.tempFiles.Store(defaultXMLPathSharedStrings, "")
assert.Equal(t, "1", f.getFromStringItem(1))
// Test get from string item with invalid offset range
f.sharedStringItem = [][]uint{{0}}
assert.Equal(t, "0", f.getFromStringItem(0))
// Cleanup undelete temporary files
assert.NoError(t, os.Remove(tempFile.(string)))
// Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err)
f.tempFiles.Store(defaultXMLPathSharedStrings, "")
err = f.SetCellRichText("Sheet1", "A19", []RichTextRun{})
assert.Error(t, err)
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
const maxUint16 = 1<<16 - 1
currentRow := 0
for rows.Next() {
currentRow++
if currentRow == 19 {
_, err := rows.Columns()
assert.NoError(t, err)
// Test get cell value from string item with invalid offset
f.sharedStringItem[1] = []uint{maxUint16 - 1, maxUint16}
assert.Equal(t, "1", f.getFromStringItem(1))
break
}
}
assert.NoError(t, rows.Close())
// Test shared string item temporary files has been closed before close the workbook
assert.NoError(t, f.sharedStringTemp.Close())
assert.Error(t, f.Close())
// Cleanup undelete temporary files
f.tempFiles.Range(func(k, v interface{}) bool {
return assert.NoError(t, os.Remove(v.(string)))
})
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
rows, err = f.Rows("Sheet1")
assert.NoError(t, err)
currentRow = 0
for rows.Next() {
currentRow++
if currentRow == 19 {
_, err := rows.Columns()
assert.NoError(t, err)
break
}
}
assert.NoError(t, rows.Close())
assert.NoError(t, f.sharedStringTemp.Close())
// Test shared string item temporary files has been closed before set the cell value
assert.Error(t, f.SetCellValue("Sheet1", "A1", "A1"))
assert.Error(t, f.Close())
// Cleanup undelete temporary files
f.tempFiles.Range(func(k, v interface{}) bool {
return assert.NoError(t, os.Remove(v.(string)))
})
}
func TestSetCellIntFunc(t *testing.T) {
cases := []struct {
val interface{}
target string
}{
{val: 128, target: "128"},
{val: int8(-128), target: "-128"},
{val: int16(-32768), target: "-32768"},
{val: int32(-2147483648), target: "-2147483648"},
{val: int64(-9223372036854775808), target: "-9223372036854775808"},
{val: uint(128), target: "128"},
{val: uint8(255), target: "255"},
{val: uint16(65535), target: "65535"},
{val: uint32(4294967295), target: "4294967295"},
{val: uint64(18446744073709551615), target: "18446744073709551615"},
}
for _, c := range cases {
cell := &xlsxC{}
setCellIntFunc(cell, c.val)
assert.Equal(t, c.target, cell.V)
}
}
func TestSIString(t *testing.T) {
assert.Empty(t, xlsxSI{}.String())
}
func TestGetCellStringFunc(t *testing.T) {
f := NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row = []xlsxRow{{R: 2}}
val, err := f.GetCellValue("Sheet1", "A1")
assert.Empty(t, val)
assert.NoError(t, err)
assert.NoError(t, f.Close())
}
================================================
FILE: chart.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"fmt"
"strconv"
"strings"
)
// ChartType is the type of supported chart types.
type ChartType byte
// This section defines the currently supported chart types enumeration.
const (
Area ChartType = iota
AreaStacked
AreaPercentStacked
Area3D
Area3DStacked
Area3DPercentStacked
Bar
BarStacked
BarPercentStacked
Bar3DClustered
Bar3DStacked
Bar3DPercentStacked
Bar3DConeClustered
Bar3DConeStacked
Bar3DConePercentStacked
Bar3DPyramidClustered
Bar3DPyramidStacked
Bar3DPyramidPercentStacked
Bar3DCylinderClustered
Bar3DCylinderStacked
Bar3DCylinderPercentStacked
Col
ColStacked
ColPercentStacked
Col3D
Col3DClustered
Col3DStacked
Col3DPercentStacked
Col3DCone
Col3DConeClustered
Col3DConeStacked
Col3DConePercentStacked
Col3DPyramid
Col3DPyramidClustered
Col3DPyramidStacked
Col3DPyramidPercentStacked
Col3DCylinder
Col3DCylinderClustered
Col3DCylinderStacked
Col3DCylinderPercentStacked
Doughnut
Line
Line3D
Pie
Pie3D
PieOfPie
BarOfPie
Radar
Scatter
Surface3D
WireframeSurface3D
Contour
WireframeContour
Bubble
Bubble3D
StockHighLowClose
StockOpenHighLowClose
)
// ChartDashType is the type of supported chart dash types.
type ChartDashType byte
// This section defines the currently supported chart dash types enumeration.
const (
ChartDashUnset ChartDashType = iota
ChartDashSolid
ChartDashDot
ChartDashDash
ChartDashLgDash
ChartDashSashDot
ChartDashLgDashDot
ChartDashLgDashDotDot
ChartDashSysDash
ChartDashSysDot
ChartDashSysDashDot
ChartDashSysDashDotDot
)
// ChartLineType is the type of supported chart line types.
type ChartLineType byte
// This section defines the currently supported chart line types enumeration.
const (
ChartLineUnset ChartLineType = iota
ChartLineSolid
ChartLineNone
ChartLineAutomatic
)
// ChartTickLabelPositionType is the type of supported chart tick label position
// types.
type ChartTickLabelPositionType byte
// This section defines the supported chart tick label position types
// enumeration.
const (
ChartTickLabelNextToAxis ChartTickLabelPositionType = iota
ChartTickLabelHigh
ChartTickLabelLow
ChartTickLabelNone
)
// This section defines the default value of chart properties.
var (
chartView3DRotX = map[ChartType]int{
Area: 0,
AreaStacked: 0,
AreaPercentStacked: 0,
Area3D: 15,
Area3DStacked: 15,
Area3DPercentStacked: 15,
Bar: 0,
BarStacked: 0,
BarPercentStacked: 0,
Bar3DClustered: 15,
Bar3DStacked: 15,
Bar3DPercentStacked: 15,
Bar3DConeClustered: 15,
Bar3DConeStacked: 15,
Bar3DConePercentStacked: 15,
Bar3DPyramidClustered: 15,
Bar3DPyramidStacked: 15,
Bar3DPyramidPercentStacked: 15,
Bar3DCylinderClustered: 15,
Bar3DCylinderStacked: 15,
Bar3DCylinderPercentStacked: 15,
Col: 0,
ColStacked: 0,
ColPercentStacked: 0,
Col3D: 15,
Col3DClustered: 15,
Col3DStacked: 15,
Col3DPercentStacked: 15,
Col3DCone: 15,
Col3DConeClustered: 15,
Col3DConeStacked: 15,
Col3DConePercentStacked: 15,
Col3DPyramid: 15,
Col3DPyramidClustered: 15,
Col3DPyramidStacked: 15,
Col3DPyramidPercentStacked: 15,
Col3DCylinder: 15,
Col3DCylinderClustered: 15,
Col3DCylinderStacked: 15,
Col3DCylinderPercentStacked: 15,
Doughnut: 0,
Line: 0,
Line3D: 20,
Pie: 0,
Pie3D: 30,
PieOfPie: 0,
BarOfPie: 0,
Radar: 0,
Scatter: 0,
Surface3D: 15,
WireframeSurface3D: 15,
Contour: 90,
WireframeContour: 90,
}
chartView3DRotY = map[ChartType]int{
Area: 0,
AreaStacked: 0,
AreaPercentStacked: 0,
Area3D: 20,
Area3DStacked: 20,
Area3DPercentStacked: 20,
Bar: 0,
BarStacked: 0,
BarPercentStacked: 0,
Bar3DClustered: 20,
Bar3DStacked: 20,
Bar3DPercentStacked: 20,
Bar3DConeClustered: 20,
Bar3DConeStacked: 20,
Bar3DConePercentStacked: 20,
Bar3DPyramidClustered: 20,
Bar3DPyramidStacked: 20,
Bar3DPyramidPercentStacked: 20,
Bar3DCylinderClustered: 20,
Bar3DCylinderStacked: 20,
Bar3DCylinderPercentStacked: 20,
Col: 0,
ColStacked: 0,
ColPercentStacked: 0,
Col3D: 20,
Col3DClustered: 20,
Col3DStacked: 20,
Col3DPercentStacked: 20,
Col3DCone: 20,
Col3DConeClustered: 20,
Col3DConeStacked: 20,
Col3DConePercentStacked: 20,
Col3DPyramid: 20,
Col3DPyramidClustered: 20,
Col3DPyramidStacked: 20,
Col3DPyramidPercentStacked: 20,
Col3DCylinder: 20,
Col3DCylinderClustered: 20,
Col3DCylinderStacked: 20,
Col3DCylinderPercentStacked: 20,
Doughnut: 0,
Line: 0,
Line3D: 15,
Pie: 0,
Pie3D: 0,
PieOfPie: 0,
BarOfPie: 0,
Radar: 0,
Scatter: 0,
Surface3D: 20,
WireframeSurface3D: 20,
Contour: 0,
WireframeContour: 0,
}
plotAreaChartOverlap = map[ChartType]int{
BarStacked: 100,
BarPercentStacked: 100,
ColStacked: 100,
ColPercentStacked: 100,
}
chartView3DPerspective = map[ChartType]int{
Line3D: 30,
Contour: 0,
WireframeContour: 0,
}
chartView3DRAngAx = map[ChartType]int{
Area: 0,
AreaStacked: 0,
AreaPercentStacked: 0,
Area3D: 1,
Area3DStacked: 1,
Area3DPercentStacked: 1,
Bar: 0,
BarStacked: 0,
BarPercentStacked: 0,
Bar3DClustered: 1,
Bar3DStacked: 1,
Bar3DPercentStacked: 1,
Bar3DConeClustered: 1,
Bar3DConeStacked: 1,
Bar3DConePercentStacked: 1,
Bar3DPyramidClustered: 1,
Bar3DPyramidStacked: 1,
Bar3DPyramidPercentStacked: 1,
Bar3DCylinderClustered: 1,
Bar3DCylinderStacked: 1,
Bar3DCylinderPercentStacked: 1,
Col: 0,
ColStacked: 0,
ColPercentStacked: 0,
Col3D: 1,
Col3DClustered: 1,
Col3DStacked: 1,
Col3DPercentStacked: 1,
Col3DCone: 1,
Col3DConeClustered: 1,
Col3DConeStacked: 1,
Col3DConePercentStacked: 1,
Col3DPyramid: 1,
Col3DPyramidClustered: 1,
Col3DPyramidStacked: 1,
Col3DPyramidPercentStacked: 1,
Col3DCylinder: 1,
Col3DCylinderClustered: 1,
Col3DCylinderStacked: 1,
Col3DCylinderPercentStacked: 1,
Doughnut: 0,
Line: 0,
Line3D: 0,
Pie: 0,
Pie3D: 0,
PieOfPie: 0,
BarOfPie: 0,
Radar: 0,
Scatter: 0,
Surface3D: 0,
WireframeSurface3D: 0,
Contour: 0,
Bubble: 0,
Bubble3D: 0,
}
chartLegendPosition = map[string]string{
"bottom": "b",
"left": "l",
"right": "r",
"top": "t",
"top_right": "tr",
}
chartValAxNumFmtFormatCode = map[ChartType]string{
Area: "General",
AreaStacked: "General",
AreaPercentStacked: "0%",
Area3D: "General",
Area3DStacked: "General",
Area3DPercentStacked: "0%",
Bar: "General",
BarStacked: "General",
BarPercentStacked: "0%",
Bar3DClustered: "General",
Bar3DStacked: "General",
Bar3DPercentStacked: "0%",
Bar3DConeClustered: "General",
Bar3DConeStacked: "General",
Bar3DConePercentStacked: "0%",
Bar3DPyramidClustered: "General",
Bar3DPyramidStacked: "General",
Bar3DPyramidPercentStacked: "0%",
Bar3DCylinderClustered: "General",
Bar3DCylinderStacked: "General",
Bar3DCylinderPercentStacked: "0%",
Col: "General",
ColStacked: "General",
ColPercentStacked: "0%",
Col3D: "General",
Col3DClustered: "General",
Col3DStacked: "General",
Col3DPercentStacked: "0%",
Col3DCone: "General",
Col3DConeClustered: "General",
Col3DConeStacked: "General",
Col3DConePercentStacked: "0%",
Col3DPyramid: "General",
Col3DPyramidClustered: "General",
Col3DPyramidStacked: "General",
Col3DPyramidPercentStacked: "0%",
Col3DCylinder: "General",
Col3DCylinderClustered: "General",
Col3DCylinderStacked: "General",
Col3DCylinderPercentStacked: "0%",
Doughnut: "General",
Line: "General",
Line3D: "General",
Pie: "General",
Pie3D: "General",
PieOfPie: "General",
BarOfPie: "General",
Radar: "General",
Scatter: "General",
Surface3D: "General",
WireframeSurface3D: "General",
Contour: "General",
WireframeContour: "General",
Bubble: "General",
Bubble3D: "General",
StockHighLowClose: "General",
StockOpenHighLowClose: "General",
}
chartValAxCrossBetween = map[ChartType]string{
Area: "midCat",
AreaStacked: "midCat",
AreaPercentStacked: "midCat",
Area3D: "midCat",
Area3DStacked: "midCat",
Area3DPercentStacked: "midCat",
Bar: "between",
BarStacked: "between",
BarPercentStacked: "between",
Bar3DClustered: "between",
Bar3DStacked: "between",
Bar3DPercentStacked: "between",
Bar3DConeClustered: "between",
Bar3DConeStacked: "between",
Bar3DConePercentStacked: "between",
Bar3DPyramidClustered: "between",
Bar3DPyramidStacked: "between",
Bar3DPyramidPercentStacked: "between",
Bar3DCylinderClustered: "between",
Bar3DCylinderStacked: "between",
Bar3DCylinderPercentStacked: "between",
Col: "between",
ColStacked: "between",
ColPercentStacked: "between",
Col3D: "between",
Col3DClustered: "between",
Col3DStacked: "between",
Col3DPercentStacked: "between",
Col3DCone: "between",
Col3DConeClustered: "between",
Col3DConeStacked: "between",
Col3DConePercentStacked: "between",
Col3DPyramid: "between",
Col3DPyramidClustered: "between",
Col3DPyramidStacked: "between",
Col3DPyramidPercentStacked: "between",
Col3DCylinder: "between",
Col3DCylinderClustered: "between",
Col3DCylinderStacked: "between",
Col3DCylinderPercentStacked: "between",
Doughnut: "between",
Line: "between",
Line3D: "between",
Pie: "between",
Pie3D: "between",
PieOfPie: "between",
BarOfPie: "between",
Radar: "between",
Scatter: "between",
Surface3D: "midCat",
WireframeSurface3D: "midCat",
Contour: "midCat",
WireframeContour: "midCat",
Bubble: "midCat",
Bubble3D: "midCat",
StockHighLowClose: "between",
StockOpenHighLowClose: "between",
}
plotAreaChartGrouping = map[ChartType]string{
Area: "standard",
AreaStacked: "stacked",
AreaPercentStacked: "percentStacked",
Area3D: "standard",
Area3DStacked: "stacked",
Area3DPercentStacked: "percentStacked",
Bar: "clustered",
BarStacked: "stacked",
BarPercentStacked: "percentStacked",
Bar3DClustered: "clustered",
Bar3DStacked: "stacked",
Bar3DPercentStacked: "percentStacked",
Bar3DConeClustered: "clustered",
Bar3DConeStacked: "stacked",
Bar3DConePercentStacked: "percentStacked",
Bar3DPyramidClustered: "clustered",
Bar3DPyramidStacked: "stacked",
Bar3DPyramidPercentStacked: "percentStacked",
Bar3DCylinderClustered: "clustered",
Bar3DCylinderStacked: "stacked",
Bar3DCylinderPercentStacked: "percentStacked",
Col: "clustered",
ColStacked: "stacked",
ColPercentStacked: "percentStacked",
Col3D: "standard",
Col3DClustered: "clustered",
Col3DStacked: "stacked",
Col3DPercentStacked: "percentStacked",
Col3DCone: "standard",
Col3DConeClustered: "clustered",
Col3DConeStacked: "stacked",
Col3DConePercentStacked: "percentStacked",
Col3DPyramid: "standard",
Col3DPyramidClustered: "clustered",
Col3DPyramidStacked: "stacked",
Col3DPyramidPercentStacked: "percentStacked",
Col3DCylinder: "standard",
Col3DCylinderClustered: "clustered",
Col3DCylinderStacked: "stacked",
Col3DCylinderPercentStacked: "percentStacked",
Line: "standard",
Line3D: "standard",
}
plotAreaChartBarDir = map[ChartType]string{
Bar: "bar",
BarStacked: "bar",
BarPercentStacked: "bar",
Bar3DClustered: "bar",
Bar3DStacked: "bar",
Bar3DPercentStacked: "bar",
Bar3DConeClustered: "bar",
Bar3DConeStacked: "bar",
Bar3DConePercentStacked: "bar",
Bar3DPyramidClustered: "bar",
Bar3DPyramidStacked: "bar",
Bar3DPyramidPercentStacked: "bar",
Bar3DCylinderClustered: "bar",
Bar3DCylinderStacked: "bar",
Bar3DCylinderPercentStacked: "bar",
Col: "col",
ColStacked: "col",
ColPercentStacked: "col",
Col3D: "col",
Col3DClustered: "col",
Col3DStacked: "col",
Col3DPercentStacked: "col",
Col3DCone: "col",
Col3DConeStacked: "col",
Col3DConeClustered: "col",
Col3DConePercentStacked: "col",
Col3DPyramid: "col",
Col3DPyramidClustered: "col",
Col3DPyramidStacked: "col",
Col3DPyramidPercentStacked: "col",
Col3DCylinder: "col",
Col3DCylinderClustered: "col",
Col3DCylinderStacked: "col",
Col3DCylinderPercentStacked: "col",
Line: "standard",
Line3D: "standard",
}
barColChartTypes = []ChartType{
Bar,
BarStacked,
BarPercentStacked,
Bar3DClustered,
Bar3DStacked,
Bar3DPercentStacked,
Bar3DConeClustered,
Bar3DConeStacked,
Bar3DConePercentStacked,
Bar3DPyramidClustered,
Bar3DPyramidStacked,
Bar3DPyramidPercentStacked,
Bar3DCylinderClustered,
Bar3DCylinderStacked,
Bar3DCylinderPercentStacked,
Col,
ColStacked,
ColPercentStacked,
Col3D,
Col3DClustered,
Col3DStacked,
Col3DPercentStacked,
Col3DCone,
Col3DConeStacked,
Col3DConeClustered,
Col3DConePercentStacked,
Col3DPyramid,
Col3DPyramidClustered,
Col3DPyramidStacked,
Col3DPyramidPercentStacked,
Col3DCylinder,
Col3DCylinderClustered,
Col3DCylinderStacked,
Col3DCylinderPercentStacked,
}
orientation = map[bool]string{
true: "maxMin",
false: "minMax",
}
catAxPos = map[bool]string{
true: "t",
false: "b",
}
valAxPos = map[bool]string{
true: "r",
false: "l",
}
tickLblPosVal = map[ChartTickLabelPositionType]string{
ChartTickLabelNextToAxis: "nextTo",
ChartTickLabelHigh: "high",
ChartTickLabelLow: "low",
ChartTickLabelNone: "none",
}
tickLblPosNone = map[ChartType]string{
Contour: "none",
WireframeContour: "none",
}
)
// parseChartOptions provides a function to parse the format settings of the
// chart with default value.
func parseChartOptions(opts *Chart) (*Chart, error) {
if opts == nil {
return nil, ErrParameterInvalid
}
if opts.Dimension.Width == 0 {
opts.Dimension.Width = defaultChartDimensionWidth
}
if opts.Dimension.Height == 0 {
opts.Dimension.Height = defaultChartDimensionHeight
}
if opts.Legend.Position == "" {
opts.Legend.Position = defaultChartLegendPosition
}
opts.parseTitle()
if opts.Fill.Transparency < 0 || 100 < opts.Fill.Transparency {
return opts, ErrTransparency
}
if opts.VaryColors == nil {
opts.VaryColors = boolPtr(true)
}
if opts.Border.Width == 0 {
opts.Border.Width = 0.75
}
if opts.ShowBlanksAs == "" {
opts.ShowBlanksAs = defaultChartShowBlanksAs
}
format := opts.Format
graphicOptions, err := format.parseGraphicOptions(nil)
if err != nil {
return opts, err
}
opts.Format = *graphicOptions
return opts, opts.parseSeries()
}
// parseSeries check the series settings of the chart.
func (opts *Chart) parseSeries() error {
for _, series := range opts.Series {
if series.Fill.Transparency < 0 || 100 < series.Fill.Transparency {
return ErrTransparency
}
}
return nil
}
// parseTitle parse the title settings of the chart with default value.
func (opts *Chart) parseTitle() {
for i := range opts.Title {
if opts.Title[i].Font == nil {
opts.Title[i].Font = &Font{}
}
if opts.Title[i].Font.Color == "" {
opts.Title[i].Font.Color = "595959"
}
if opts.Title[i].Font.Size == 0 {
opts.Title[i].Font.Size = 14
}
}
}
// AddChart provides the method to add chart in a sheet by given chart format
// set (such as offset, scale, aspect ratio setting and print settings) and
// properties set. For example, create 3D clustered column chart with data
// Sheet1!$E$1:$L$15:
//
// package main
//
// import (
// "fmt"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// for idx, row := range [][]interface{}{
// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
// } {
// cell, err := excelize.CoordinatesToCellName(1, idx+1)
// if err != nil {
// fmt.Println(err)
// return
// }
// f.SetSheetRow("Sheet1", cell, &row)
// }
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
// Type: excelize.Col3DClustered,
// Series: []excelize.ChartSeries{
// {
// 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: []excelize.RichTextRun{
// {
// Text: "Fruit 3D Clustered Column Chart",
// },
// },
// Legend: excelize.ChartLegend{
// ShowLegendKey: false,
// },
// PlotArea: excelize.ChartPlotArea{
// ShowBubbleSize: true,
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
// // Save spreadsheet by the given path.
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
//
// The following shows the type of chart supported by excelize:
//
// ID | Enumeration | Chart
// ----+-----------------------------+------------------------------
// 0 | Area | 2D area chart
// 1 | AreaStacked | 2D stacked area chart
// 2 | AreaPercentStacked | 2D 100% stacked area chart
// 3 | Area3D | 3D area chart
// 4 | Area3DStacked | 3D stacked area chart
// 5 | Area3DPercentStacked | 3D 100% stacked area chart
// 6 | Bar | 2D clustered bar chart
// 7 | BarStacked | 2D stacked bar chart
// 8 | BarPercentStacked | 2D 100% stacked bar chart
// 9 | Bar3DClustered | 3D clustered bar chart
// 10 | Bar3DStacked | 3D stacked bar chart
// 11 | Bar3DPercentStacked | 3D 100% stacked bar chart
// 12 | Bar3DConeClustered | 3D cone clustered bar chart
// 13 | Bar3DConeStacked | 3D cone stacked bar chart
// 14 | Bar3DConePercentStacked | 3D cone percent bar chart
// 15 | Bar3DPyramidClustered | 3D pyramid clustered bar chart
// 16 | Bar3DPyramidStacked | 3D pyramid stacked bar chart
// 17 | Bar3DPyramidPercentStacked | 3D pyramid percent stacked bar chart
// 18 | Bar3DCylinderClustered | 3D cylinder clustered bar chart
// 19 | Bar3DCylinderStacked | 3D cylinder stacked bar chart
// 20 | Bar3DCylinderPercentStacked | 3D cylinder percent stacked bar chart
// 21 | Col | 2D clustered column chart
// 22 | ColStacked | 2D stacked column chart
// 23 | ColPercentStacked | 2D 100% stacked column chart
// 24 | Col3DClustered | 3D clustered column chart
// 25 | Col3D | 3D column chart
// 26 | Col3DStacked | 3D stacked column chart
// 27 | Col3DPercentStacked | 3D 100% stacked column chart
// 28 | Col3DCone | 3D cone column chart
// 29 | Col3DConeClustered | 3D cone clustered column chart
// 30 | Col3DConeStacked | 3D cone stacked column chart
// 31 | Col3DConePercentStacked | 3D cone percent stacked column chart
// 32 | Col3DPyramid | 3D pyramid column chart
// 33 | Col3DPyramidClustered | 3D pyramid clustered column chart
// 34 | Col3DPyramidStacked | 3D pyramid stacked column chart
// 35 | Col3DPyramidPercentStacked | 3D pyramid percent stacked column chart
// 36 | Col3DCylinder | 3D cylinder column chart
// 37 | Col3DCylinderClustered | 3D cylinder clustered column chart
// 38 | Col3DCylinderStacked | 3D cylinder stacked column chart
// 39 | Col3DCylinderPercentStacked | 3D cylinder percent stacked column chart
// 40 | Doughnut | doughnut chart
// 41 | Line | line chart
// 42 | Line3D | 3D line chart
// 43 | Pie | pie chart
// 44 | Pie3D | 3D pie chart
// 45 | PieOfPie | pie of pie chart
// 46 | BarOfPie | bar of pie chart
// 47 | Radar | radar chart
// 48 | Scatter | scatter chart
// 49 | Surface3D | 3D surface chart
// 50 | WireframeSurface3D | 3D wireframe surface chart
// 51 | Contour | contour chart
// 52 | WireframeContour | wireframe contour chart
// 53 | Bubble | bubble chart
// 54 | Bubble3D | 3D bubble chart
// 55 | StockHighLowClose | High-Low-Close stock chart
// 56 | StockOpenHighLowClose | Open-High-Low-Close stock chart
//
// In Excel a chart series is a collection of information that defines which
// data is plotted such as values, axis labels and formatting.
//
// The series options that can be set are:
//
// Name
// Categories
// Values
// Fill
// Legend
// Line
// Marker
// DataLabel
// DataLabelPosition
// DataPoint
//
// 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
//
// 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.
//
// 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.
//
// Sizes: This sets the bubble size in a data series. The 'Sizes' property is
// optional and the default value was same with 'Values'.
//
// Fill: This set the format for the data series fill. The 'Fill' property is
// optional.
//
// Legend: This set the font of legend text for a data series. The 'Legend'
// property is optional.
//
// Line: This sets the line format of the line chart. The 'Line' property is
// optional and if it isn't supplied it will default style. The options that
// can be set are width and color. The range of width is 0.25pt - 999pt. If the
// value of width is outside the range, the default width of the line is 2pt.
//
// Marker: This sets the marker of the line chart and scatter chart. The range
// of optional field 'Size' is 2-72 (default value is 5). The enumeration value
// of optional field 'Symbol' are (default value is 'auto'):
//
// circle
// dash
// diamond
// dot
// none
// picture
// plus
// square
// star
// triangle
// x
// auto
//
// DataLabel: This sets the format of the chart series data label.
//
// DataLabelPosition: This sets the position of the chart series data label.
//
// DataPoint: This sets the format for individual data points in a doughnut, pie
// or 3D pie chart series. The 'DataPoint' property is optional.
//
// Set properties of the chart legend. The options that can be set are:
//
// Position
// ShowLegendKey
// Font
//
// Position: Set the position of the chart legend. The default legend position
// is bottom. The available positions are:
//
// none
// top
// bottom
// left
// right
// top_right
//
// ShowLegendKey: Set the legend keys shall be shown in data labels. The default
// value is false.
//
// Font: Set the font properties of the chart legend text. The properties that
// can be set are the same as the font object that is used for cell formatting.
// The font family, size, color, bold, italic, underline, and strike properties
// can be set.
//
// Set properties of the chart title. The properties that can be set are:
//
// Title
//
// Title: 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
// sheet name. The name property is optional. The default is to have no chart
// title.
//
// Specifies how blank cells are plotted on the chart by 'ShowBlanksAs'. The
// default value is gap. The options that can be set are:
//
// gap
// span
// zero
//
// gap: Specifies that blank values shall be left as a gap.
//
// span: Specifies that blank values shall be spanned with a line.
//
// zero: Specifies that blank values shall be treated as zero.
//
// Specifies that each data marker in the series has a different color by
// 'VaryColors'. The default value is true.
//
// Set chart offset, scale, aspect ratio setting and print settings by 'Format',
// same as function 'AddPicture'.
//
// Set the position of the chart plot area by 'PlotArea'. The properties that
// can be set are:
//
// SecondPlotValues
// ShowBubbleSize
// ShowCatName
// ShowDataTable
// ShowDataTableKeys
// ShowLeaderLines
// ShowPercent
// ShowSerName
// ShowVal
// Fill
// UpBars
// DownBars
// NumFmt
//
// SecondPlotValues: Specifies the values in second plot for the 'PieOfPie' and
// 'BarOfPie' chart.
//
// ShowBubbleSize: Specifies the bubble size shall be shown in a data label. The
// 'ShowBubbleSize' property is optional. The default value is false.
//
// ShowCatName: Specifies that the category name shall be shown in the data
// label. The 'ShowCatName' property is optional. The default value is true.
//
// ShowDataTable: Used for add data table under chart, depending on the chart
// type, only available for area, bar, column and line series type charts. The
// 'ShowDataTable' property is optional. The default value is false.
//
// ShowDataTableKeys: Used for add legend key in data table, only works on
// 'ShowDataTable' is enabled. The 'ShowDataTableKeys' property is optional.
// The default value is false.
//
// ShowLeaderLines: Specifies leader lines shall be shown for data labels. The
// 'ShowLeaderLines' property is optional. The default value is false.
//
// ShowPercent: Specifies that the percentage shall be shown in a data label.
// The 'ShowPercent' property is optional. The default value is false.
//
// ShowSerName: Specifies that the series name shall be shown in a data label.
// The 'ShowSerName' property is optional. The default value is false.
//
// ShowVal: Specifies that the value shall be shown in a data label.
// The 'ShowVal' property is optional. The default value is false.
//
// Fill: Set fill color of the chart.
//
// UpBars: Specifies the format for stock chart up bars. The 'UpBars' property
// is optional.
//
// DownBars: Specifies the format for stock chart down bars. The 'DownBars'
// property is optional.
//
// NumFmt: Specifies that if linked to source and set custom number format code
// for data labels. The 'NumFmt' property is optional. The default format code
// is 'General'.
//
// Set the primary horizontal and vertical axis options by 'XAxis' and 'YAxis'.
// The properties of 'XAxis' that can be set are:
//
// None
// DropLines
// HighLowLines
// MajorGridLines
// MinorGridLines
// TickLabelSkip
// ReverseOrder
// Maximum
// Minimum
// Alignment
// Font
// NumFmt
// Title
//
// The properties of 'YAxis' that can be set are:
//
// None
// MajorGridLines
// MinorGridLines
// MajorUnit
// Secondary
// ReverseOrder
// Maximum
// Minimum
// Alignment
// Font
// LogBase
// NumFmt
// Title
//
// None: Disable axes.
//
// DropLines: Specifies drop lines for the 2D and 3D area and line charts. Drop
// lines are vertical lines that connect data points in a chart down to the
// horizontal (category) axis. They are often used in Line or Area charts to
// make it easier to see the exact category position of each point. The
// 'DropLines' property is optional. The default value is false.
//
// HighLowLines: Specifies high low lines for the 2D line chart. High low lines
// displayed by default in stock charts. They extend from the highest value to
// the lowest value in each category. The 'HighLowLines' property is optional.
// The default value is false.
//
// MajorGridLines: Specifies major grid lines.
//
// MinorGridLines: Specifies minor grid lines.
//
// MajorUnit: Specifies the distance between major ticks. Shall contain a
// positive floating-point number. The 'MajorUnit' property is optional. The
// default value is auto.
//
// Secondary: Specifies the current series vertical axis as the secondary axis,
// this only works for the second and later chart in the combo chart. The
// default value is false.
//
// TickLabelSkip: Specifies how many tick labels to skip between label that is
// drawn. The 'TickLabelSkip' property is optional. The default value is auto.
//
// ReverseOrder: Specifies that the categories or values on reverse order
// (orientation of the chart). The 'ReverseOrder' property is optional. The
// default value is false.
//
// Maximum: Specifies that the fixed maximum, 0 is auto. The 'Maximum' property
// is optional. The default value is auto.
//
// Minimum: Specifies that the fixed minimum, 0 is auto. The 'Minimum' property
// is optional. The default value is auto.
//
// Alignment: Specifies that the alignment of the horizontal and vertical axis.
// The properties of alignment that can be set are:
//
// TextRotation
// Vertical
//
// The value of 'TextRotation' that can be set from -90 to 90.
//
// The value of 'Vertical' that can be set are:
//
// horz
// vert
// vert270
// wordArtVert
// eaVert
// mongolianVert
// wordArtVertRtl
//
// Font: Specifies that the font of the horizontal and vertical axis. The
// properties of font that can be set are:
//
// Bold
// Italic
// Underline
// Family
// Size
// Strike
// Color
// VertAlign
//
// LogBase: Specifies logarithmic scale base number of the vertical axis.
//
// NumFmt: Specifies that if linked to source and set custom number format code
// for axis. The 'NumFmt' property is optional. The default format code is
// 'General'.
//
// Title: Specifies that the primary horizontal or vertical axis title and
// resize chart. The 'Title' property is optional.
//
// Set chart size by 'Dimension' property. The 'Dimension' property is optional.
// The default width is 480, and height is 260.
//
// Set chart legend for all data series by 'Legend' property. The 'Legend'
// property is optional.
//
// Set the bubble size in all data series for the bubble chart or 3D bubble
// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional. The
// default width is 100, and the value should be great than 0 and less or equal
// than 300.
//
// Set the doughnut hole size in all data series for the doughnut chart by
// 'HoleSize' property. The 'HoleSize' property is optional. The default width
// is 75, and the value should be great than 0 and less or equal than 90.
//
// Set the gap with of the column and bar series chart by 'GapWidth' property.
// The 'GapWidth' property is optional. The default width is 150, and the value
// should be great or equal than 0 and less or equal than 500.
//
// Set series overlap of the column and bar series chart by 'Overlap' property.
// The 'Overlap' property is optional. The default width is 0, and the value
// should be great or equal than -100 and less or equal than 100.
//
// combo: Specifies the create a chart that combines two or more chart types in
// a single chart. For example, create a clustered column - line chart with
// data Sheet1!$E$1:$L$15:
//
// package main
//
// import (
// "fmt"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// for idx, row := range [][]interface{}{
// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
// } {
// cell, err := excelize.CoordinatesToCellName(1, idx+1)
// if err != nil {
// fmt.Println(err)
// return
// }
// f.SetSheetRow("Sheet1", cell, &row)
// }
// enable, disable := true, false
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
// Type: excelize.Col,
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$2",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$2:$D$2",
// },
// },
// Format: excelize.GraphicOptions{
// ScaleX: 1,
// ScaleY: 1,
// OffsetX: 15,
// OffsetY: 10,
// PrintObject: &enable,
// LockAspectRatio: false,
// Locked: &disable,
// },
// Title: []excelize.RichTextRun{
// {
// Text: "Clustered Column - Line Chart",
// },
// },
// Legend: excelize.ChartLegend{
// Position: "left",
// ShowLegendKey: false,
// },
// PlotArea: excelize.ChartPlotArea{
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }, &excelize.Chart{
// Type: excelize.Line,
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$4",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$4:$D$4",
// Marker: excelize.ChartMarker{
// Symbol: "none", Size: 10,
// },
// },
// },
// Format: excelize.GraphicOptions{
// ScaleX: 1,
// ScaleY: 1,
// OffsetX: 15,
// OffsetY: 10,
// PrintObject: &enable,
// LockAspectRatio: false,
// Locked: &disable,
// },
// Legend: excelize.ChartLegend{
// Position: "right",
// ShowLegendKey: false,
// },
// PlotArea: excelize.ChartPlotArea{
// ShowCatName: false,
// ShowLeaderLines: false,
// ShowPercent: true,
// ShowSerName: true,
// ShowVal: true,
// },
// }); err != nil {
// fmt.Println(err)
// return
// }
// // Save spreadsheet by the given path.
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddChart(sheet, cell string, chart *Chart, combo ...*Chart) error {
// Read worksheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil {
return err
}
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
drawingID := f.countDrawings() + 1
chartID := f.countCharts() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
err = f.addDrawingChart(sheet, drawingXML, cell, int(opts.Dimension.Width), int(opts.Dimension.Height), drawingRID, &opts.Format)
if err != nil {
return err
}
f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err
}
_ = f.addContentTypePart(drawingID, "drawings")
f.addSheetNameSpace(sheet, SourceRelationship)
return err
}
// AddChartSheet provides the method to create a chartsheet by given chart
// format set (such as offset, scale, aspect ratio setting and print settings)
// and properties set. In Excel a chartsheet is a worksheet that only contains
// a chart.
func (f *File) AddChartSheet(sheet string, chart *Chart, combo ...*Chart) error {
// Check if the worksheet already exists
idx, err := f.GetSheetIndex(sheet)
if err != nil {
return err
}
if idx != -1 {
return ErrExistsSheet
}
opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil {
return err
}
cs := xlsxChartsheet{
SheetViews: &xlsxChartsheetViews{
SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}},
},
}
f.SheetCount++
wb, _ := f.workbookReader()
sheetID := 0
for _, v := range wb.Sheets.Sheet {
if v.SheetID > sheetID {
sheetID = v.SheetID
}
}
sheetID++
path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml"
f.sheetMap[sheet] = path
f.Sheet.Store(path, nil)
drawingID := f.countDrawings() + 1
chartID := f.countCharts() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
f.prepareChartSheetDrawing(&cs, drawingID, sheet)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
if err = f.addSheetDrawingChart(drawingXML, drawingRID, &opts.Format); err != nil {
return err
}
f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err
}
_ = f.addContentTypePart(sheetID, "chartsheet")
_ = f.addContentTypePart(drawingID, "drawings")
// Update workbook.xml.rels
rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "")
// Update workbook.xml
f.setWorkbook(sheet, sheetID, rID)
chartsheet, _ := xml.Marshal(cs)
f.addSheetNameSpace(sheet, NameSpaceSpreadSheet)
f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet)))
return err
}
// getChartOptions provides a function to check format set of the chart and
// create chart format.
func (f *File) getChartOptions(opts *Chart, combo []*Chart) (*Chart, []*Chart, error) {
var comboCharts []*Chart
options, err := parseChartOptions(opts)
if err != nil {
return options, comboCharts, err
}
for _, comboFormat := range combo {
comboChart, err := parseChartOptions(comboFormat)
if err != nil {
return options, comboCharts, err
}
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
return options, comboCharts, newUnsupportedChartType(comboChart.Type)
}
comboCharts = append(comboCharts, comboChart)
}
if _, ok := chartValAxNumFmtFormatCode[options.Type]; !ok {
return options, comboCharts, newUnsupportedChartType(options.Type)
}
return options, comboCharts, err
}
// DeleteChart provides a function to delete chart in spreadsheet by given
// worksheet name and cell reference.
func (f *File) DeleteChart(sheet, cell string) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
col--
row--
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.Drawing == nil {
return err
}
drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
_, err = f.deleteDrawing(col, row, drawingXML, "Chart")
return err
}
// countCharts provides a function to get chart files count storage in the
// folder xl/charts.
func (f *File) countCharts() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/charts/chart") {
count++
}
return true
})
return count
}
// ptToEMUs provides a function to convert pt to EMUs, 1 pt = 12700 EMUs. The
// range of pt is 0.25pt - 999pt. If the value of pt is outside the range, the
// default EMUs will be returned.
func (f *File) ptToEMUs(pt float64) int {
if 0.25 > pt || pt > 999 {
return 25400
}
return int(12700 * pt)
}
================================================
FILE: chart_test.go
================================================
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChartSize(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
categories := map[string]string{
"A2": "Small",
"A3": "Normal",
"A4": "Large",
"B1": "Apple",
"C1": "Orange",
"D1": "Pear",
}
for cell, v := range categories {
assert.NoError(t, f.SetCellValue(sheet1, cell, v))
}
values := map[string]int{
"B2": 2,
"C2": 3,
"D2": 3,
"B3": 5,
"C3": 2,
"D3": 4,
"B4": 6,
"C4": 7,
"D4": 8,
}
for cell, v := range values {
assert.NoError(t, f.SetCellValue(sheet1, cell, v))
}
assert.NoError(t, f.AddChart("Sheet1", "E4", &Chart{
Type: Col3DClustered,
Dimension: ChartDimension{
Width: 640,
Height: 480,
},
Series: []ChartSeries{
{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: []RichTextRun{{Text: "3D Clustered Column Chart"}},
}))
var buffer bytes.Buffer
// Save spreadsheet by the given path.
assert.NoError(t, f.Write(&buffer))
newFile, err := OpenReader(&buffer)
assert.NoError(t, err)
chartsNum := newFile.countCharts()
if !assert.Equal(t, 1, chartsNum, "Expected 1 chart, actual %d", chartsNum) {
t.FailNow()
}
var (
workdir decodeWsDr
anchor decodeCellAnchor
)
content, ok := newFile.Pkg.Load("xl/drawings/drawing1.xml")
assert.True(t, ok, "Can't open the chart")
err = xml.Unmarshal(content.([]byte), &workdir)
if !assert.NoError(t, err) {
t.FailNow()
}
err = xml.Unmarshal([]byte(""+
workdir.TwoCellAnchor[0].Content+""), &anchor)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, 4, anchor.From.Col, "Expected 'from' column 4") ||
!assert.Equal(t, 3, anchor.From.Row, "Expected 'from' row 3") {
t.FailNow()
}
assert.Equal(t, 14, anchor.To.Col, "Expected 'to' column 14")
assert.Equal(t, 29, anchor.To.Row, "Expected 'to' row 29")
}
func TestAddDrawingChart(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddSheetDrawingChart(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSheetDrawingChart(path, 0, &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteDrawing(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err := f.deleteDrawing(0, 0, path, "Chart")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
f.Drawings.Store(path, &xlsxWsDr{OneCellAnchor: []*xdrCellAnchor{{
GraphicFrame: string(MacintoshCyrillicCharset),
}}})
_, err = f.deleteDrawing(0, 0, path, "Chart")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f.Drawings.Store(path, &xlsxWsDr{TwoCellAnchor: []*xdrCellAnchor{{
GraphicFrame: string(MacintoshCyrillicCharset),
}}})
_, err = f.deleteDrawing(0, 0, path, "Chart")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestAddChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
categories := map[string]string{"A30": "SS", "A31": "S", "A32": "M", "A33": "L", "A34": "LL", "A35": "XL", "A36": "XXL", "A37": "XXXL", "B29": "Apple", "C29": "Orange", "D29": "Pear"}
values := map[string]int{"B30": 1, "C30": 1, "D30": 1, "B31": 2, "C31": 2, "D31": 2, "B32": 3, "C32": 3, "D32": 3, "B33": 4, "C33": 4, "D33": 4, "B34": 5, "C34": 5, "D34": 5, "B35": 6, "C35": 6, "D35": 6, "B36": 7, "C36": 7, "D36": 7, "B37": 8, "C37": 8, "D37": 8}
for k, v := range categories {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
assert.EqualError(t, f.AddChart("Sheet1", "P1", nil), ErrParameterInvalid.Error())
// Test add chart on not exists worksheet
assert.EqualError(t, f.AddChart("SheetN", "P1", nil), "sheet SheetN does not exist")
// Test add chart with invalid positioning types
assert.Equal(t, f.AddChart("Sheet1", "P1", &Chart{
Format: GraphicOptions{Positioning: "x"},
}), newInvalidOptionalValue("Positioning", "x", supportedPositioning))
maximum, minimum, zero := 7.5, 0.5, .0
series := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{
Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37",
Marker: ChartMarker{
Fill: Fill{Type: "pattern", Color: []string{"FFFFFF"}, Pattern: 1},
Border: ChartLine{
Type: ChartLineSolid,
Dash: ChartDashDot,
Fill: Fill{Type: "pattern", Color: []string{"#FFFF00"}, Pattern: 1},
Width: 1,
},
},
Legend: ChartLegend{Font: &Font{Family: "Arial", Size: 11, Strike: true, Color: "777777"}},
},
}
series2 := []ChartSeries{
{
Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30",
Fill: Fill{Type: "pattern", Color: []string{"000000"}, Pattern: 1, Transparency: 60},
Marker: ChartMarker{Symbol: "none", Size: 10},
},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Line: ChartLine{Dash: ChartDashDash, Width: 0.25}},
}
series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}
series4 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37", DataPoint: []ChartDataPoint{
{Index: 0, Fill: Fill{Type: "pattern", Color: []string{"003F5C"}, Pattern: 1}},
{Index: 1, Fill: Fill{Type: "pattern", Color: []string{"58508D"}, Pattern: 1}},
{Index: 2, Fill: Fill{Type: "pattern", Color: []string{"BC5090"}, Pattern: 1}},
{Index: 3, Fill: Fill{Type: "pattern", Color: []string{"FF6361"}, Pattern: 1}},
}}}
series5 := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Sizes: "Sheet1!$B$30:$D$30", DataLabelPosition: ChartDataLabelsPositionAbove},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31", Sizes: "Sheet1!$B$31:$D$31", DataLabelPosition: ChartDataLabelsPositionLeft},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32", Sizes: "Sheet1!$B$32:$D$32", DataLabelPosition: ChartDataLabelsPositionBestFit},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33", Sizes: "Sheet1!$B$33:$D$33", DataLabelPosition: ChartDataLabelsPositionCenter},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34", Sizes: "Sheet1!$B$34:$D$34", DataLabelPosition: ChartDataLabelsPositionInsideBase},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35", Sizes: "Sheet1!$B$35:$D$35", DataLabelPosition: ChartDataLabelsPositionInsideEnd},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36", Sizes: "Sheet1!$B$36:$D$36", DataLabelPosition: ChartDataLabelsPositionOutsideEnd},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Sizes: "Sheet1!$B$37:$D$37", DataLabelPosition: ChartDataLabelsPositionRight},
}
format := GraphicOptions{
ScaleX: defaultDrawingScale,
ScaleY: defaultDrawingScale,
OffsetX: 15,
OffsetY: 10,
PrintObject: boolPtr(true),
LockAspectRatio: false,
Locked: boolPtr(false),
}
legend := ChartLegend{Position: "left", ShowLegendKey: false}
plotArea := ChartPlotArea{
SecondPlotValues: 3,
ShowBubbleSize: true,
ShowCatName: true,
ShowLeaderLines: false,
ShowPercent: true,
ShowSerName: true,
ShowVal: true,
Fill: Fill{Type: "pattern", Pattern: 1},
}
for _, c := range []struct {
sheetName, cell string
opts *Chart
}{
{sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: []RichTextRun{{Text: "2D Column Chart", Font: &Font{Size: 11, Family: "Calibri"}}}, PlotArea: plotArea, Border: ChartLine{Type: ChartLineNone}, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Family: "Times New Roman", Size: 15, Strike: true, Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}},
{sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: ColStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Pattern: 1}, Border: ChartLine{Type: ChartLineAutomatic}, ShowBlanksAs: "zero", GapWidth: uintPtr(10), Overlap: intPtr(100)}},
{sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: ColPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "100% Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Color: []string{"EEEEEE"}, Pattern: 1}, Border: ChartLine{Type: ChartLineSolid, Width: 2}, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "wordArtVertRtl", TextRotation: 0}}}},
{sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: Col3DClustered, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false, Font: &Font{Size: 10}}, Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: Col3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert", TextRotation: 0}}}},
{sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: Col3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: Radar, Series: series, Format: format, Legend: ChartLegend{Position: "top_right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Radar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "span"}},
{sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: Col3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF16", opts: &Chart{Type: Col3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF30", opts: &Chart{Type: Col3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AF45", opts: &Chart{Type: Col3DCone, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN1", opts: &Chart{Type: Col3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN16", opts: &Chart{Type: Col3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN30", opts: &Chart{Type: Col3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AN45", opts: &Chart{Type: Col3DPyramid, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Pyramid Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV1", opts: &Chart{Type: Col3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: Col3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: Col3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "AV45", opts: &Chart{Type: Col3DCylinder, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: Col3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert270", TextRotation: 0}}}},
{sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: Line3D, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true, MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}}},
{sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: Scatter, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Scatter Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: Doughnut, Series: series3, Format: format, Legend: ChartLegend{Position: "right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Doughnut Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: false, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "zero", HoleSize: 30}},
{sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: Line, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true, HighLowLines: true, MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, TickLabelPosition: ChartTickLabelLow}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}},
{sheetName: "Sheet2", cell: "P32", opts: &Chart{Type: Pie3D, Series: series3, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: Pie, Series: series4, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Pie Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false, NumFmt: ChartNumFmt{CustomNumFmt: "0.00%;0;;"}}, ShowBlanksAs: "gap"}},
// bar series chart
{sheetName: "Sheet2", cell: "P48", opts: &Chart{Type: Bar, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X48", opts: &Chart{Type: BarStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: BarPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked 100% Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: Bar3DClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: Bar3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{Maximum: &maximum, Minimum: &minimum}}},
{sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: Bar3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Secondary: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}},
// area series chart
{sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: Area, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
{sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: AreaStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
{sheetName: "Sheet2", cell: "AF16", opts: &Chart{Type: AreaPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
{sheetName: "Sheet2", cell: "AN16", opts: &Chart{Type: Area3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
{sheetName: "Sheet2", cell: "AF32", opts: &Chart{Type: Area3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
{sheetName: "Sheet2", cell: "AN32", opts: &Chart{Type: Area3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{DropLines: true}}},
// cylinder series chart
{sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: Bar3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: Bar3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: Bar3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// cone series chart
{sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: Bar3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: Bar3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: Bar3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: Bar3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: Bar3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: Bar3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// surface series chart
{sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: Surface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}},
{sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: WireframeSurface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Wireframe Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}},
{sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: Contour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
{sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: WireframeContour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Wireframe Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
// bubble chart
{sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series5, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", BubbleSize: 75}},
{sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: Bubble3D, Series: series5, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
// pie of pie chart
{sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: PieOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Pie of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
// bar of pie chart
{sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: BarOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}},
} {
assert.NoError(t, f.AddChart(c.sheetName, c.cell, c.opts))
}
// combo chart
_, err = f.NewSheet("Combo Charts")
assert.NoError(t, err)
clusteredColumnCombo := [][]interface{}{
{"A1", Line, "Clustered Column - Line Chart"},
{"I1", Doughnut, "Clustered Column - Doughnut Chart"},
}
for _, props := range clusteredColumnCombo {
assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[2].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowDataTable: true, ShowDataTableKeys: true, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}, YAxis: ChartAxis{Secondary: true}}))
}
stackedAreaCombo := map[string][]interface{}{
"A16": {Line, "Stacked Area - Line Chart"},
"I16": {Doughnut, "Stacked Area - Doughnut Chart"},
}
for axis, props := range stackedAreaCombo {
assert.NoError(t, f.AddChart("Combo Charts", axis, &Chart{Type: AreaStacked, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[1].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}))
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
// Test with invalid sheet name
assert.EqualError(t, f.AddChart("Sheet:1", "A1", &Chart{Type: Col, Series: series[:1]}), ErrSheetNameInvalid.Error())
// Test with illegal cell reference
assert.EqualError(t, f.AddChart("Sheet2", "A", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: 0x39, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newUnsupportedChartType(0x39).Error())
// Test add combo chart with invalid format set
assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}, nil), ErrParameterInvalid.Error())
// Test add combo chart with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD64", &Chart{Type: BarOfPie, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}, &Chart{Type: 0x39, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}), newUnsupportedChartType(0x39).Error())
// Test add chart with series transparency value exceeds limit
assert.Equal(t, ErrTransparency, f.AddChart("Sheet1", "BD64", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Fill: Fill{Transparency: 110}}}}))
// Test add chart with transparency value exceeds limit
assert.Equal(t, ErrTransparency, f.AddChart("Sheet1", "BD64", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Fill: Fill{Transparency: -1}}))
assert.NoError(t, f.Close())
// Test add chart with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChart("Sheet1", "P1", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8")
t.Run("for_create_stock_chart", func(t *testing.T) {
f := NewFile()
for i, row := range [][]interface{}{
{"Date", "Volume", "Open", "High", "Low", "Close"},
{45593, 14864000, 431.66, 431.94, 426.3, 426.59},
{45590, 16899100, 426.76, 432.52, 426.57, 428.15},
{45589, 13581600, 425.33, 425.98, 422.4, 424.73},
{45588, 19654400, 430.86, 431.08, 422.53, 424.6},
{45587, 25482200, 418.49, 430.58, 418.04, 427.51},
{45586, 14206100, 416.12, 418.96, 413.75, 418.78},
{45583, 17145300, 417.14, 419.65, 416.26, 418.16},
{45582, 14820000, 422.36, 422.5, 415.59, 416.72},
{45581, 15508900, 415.17, 416.36, 410.48, 416.12},
{45580, 18900200, 422.18, 422.48, 415.26, 418.74},
{45579, 16653100, 417.77, 424.04, 417.52, 419.14},
{45576, 14144900, 416.14, 417.13, 413.25, 416.32},
{45575, 13848400, 415.23, 417.35, 413.15, 415.84},
{45574, 14974300, 415.86, 420.38, 414.3, 417.46},
{45573, 19229300, 410.9, 415.66, 408.17, 414.71},
{45572, 20919800, 416, 417.11, 409, 409.54},
{45569, 19169700, 418.24, 419.75, 414.97, 416.06},
{45568, 13686400, 417.63, 419.55, 414.29, 416.54},
{45567, 16582300, 422.58, 422.82, 416.71, 417.13},
{45566, 19092900, 428.45, 428.48, 418.81, 420.69},
{45565, 16807300, 428.21, 430.42, 425.37, 430.3},
} {
cell, err := CoordinatesToCellName(1, i+1)
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet1", cell, &row))
}
style, err := f.NewStyle(&Style{NumFmt: 15})
assert.NoError(t, err)
assert.NoError(t, f.SetColStyle("Sheet1", "A", style))
assert.NoError(t, f.AddChart("Sheet1", "G1", &Chart{
Type: StockHighLowClose,
Series: []ChartSeries{
{Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"},
{Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"},
{Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"},
},
Legend: ChartLegend{Position: "none"},
Title: []RichTextRun{{Text: "High-Low-Close Stock Chart"}},
XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}},
}))
assert.NoError(t, f.AddChart("Sheet1", "G16", &Chart{
Type: StockOpenHighLowClose,
Series: []ChartSeries{
{Name: "Sheet1!$C$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$C$2:$C$22"},
{Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"},
{Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"},
{Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"},
},
Legend: ChartLegend{Position: "none"},
Title: []RichTextRun{{Text: "Open-High-Low-Close Stock Chart"}},
XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}},
PlotArea: ChartPlotArea{
UpBars: ChartUpDownBar{
Border: ChartLine{Type: ChartLineNone},
Fill: Fill{Type: "pattern", Color: []string{"00B050"}, Pattern: 1},
},
DownBars: ChartUpDownBar{
Border: ChartLine{Type: ChartLineNone},
Fill: Fill{Type: "pattern", Color: []string{"FF0000"}, Pattern: 1},
},
},
}))
assert.NoError(t, f.AddChart("Sheet1", "O1", &Chart{
Type: Col,
Series: []ChartSeries{
{Name: "Sheet1!$B$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$B$2:$B$22"},
},
VaryColors: boolPtr(false),
XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}},
YAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "#,##0"}},
Title: []RichTextRun{{Text: "Volume-High-Low-Close Stock Chart"}},
}, &Chart{
Type: StockHighLowClose,
Series: []ChartSeries{
{Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"},
{Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"},
{Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"},
},
YAxis: ChartAxis{Secondary: true},
}))
assert.NoError(t, f.AddChart("Sheet1", "O16", &Chart{
Type: Col,
Series: []ChartSeries{
{Name: "Sheet1!$B$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$B$2:$B$22"},
},
VaryColors: boolPtr(false),
XAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "d-mmm-yy"}},
YAxis: ChartAxis{NumFmt: ChartNumFmt{CustomNumFmt: "#,##0"}},
Title: []RichTextRun{{Text: "Volume-Open-High-Low-Close Stock Chart"}},
}, &Chart{
Type: StockOpenHighLowClose,
Series: []ChartSeries{
{Name: "Sheet1!$C$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$C$2:$C$22"},
{Name: "Sheet1!$D$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$D$2:$D$22"},
{Name: "Sheet1!$E$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$E$2:$E$22"},
{Name: "Sheet1!$F$1", Categories: "Sheet1!$A$2:$A$22", Values: "Sheet1!$F$2:$F$22"},
},
YAxis: ChartAxis{Secondary: true},
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartStock.xlsx")))
})
}
func TestAddChartSheet(t *testing.T) {
categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
f := NewFile()
for k, v := range categories {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v))
}
series := []ChartSeries{
{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"},
}
assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}))
// Test set the chartsheet as active sheet
var sheetIdx int
for idx, sheetName := range f.GetSheetList() {
if sheetName != "Chart1" {
continue
}
sheetIdx = idx
}
f.SetActiveSheet(sheetIdx)
// Test cell value on chartsheet
assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet")
// Test add chartsheet on already existing name sheet
assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrExistsSheet.Error())
// Test add chartsheet with invalid sheet name
assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrSheetNameInvalid.Error())
// Test with unsupported chart type
assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: 0x39, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), newUnsupportedChartType(0x39).Error())
assert.NoError(t, f.UpdateLinkedValue())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx")))
// Test add chart sheet with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChartSheet("Chart4", &Chart{Type: Col, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.DeleteChart("Sheet1", "A1"))
series := []ChartSeries{
{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"},
{Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
{Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
{Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
{Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
{Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
{Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
{Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"},
}
format := GraphicOptions{
Name: "Chart 3",
AltText: "chart",
ScaleX: defaultDrawingScale,
ScaleY: defaultDrawingScale,
OffsetX: 15,
OffsetY: 10,
PrintObject: boolPtr(true),
LockAspectRatio: false,
Locked: boolPtr(false),
}
legend := ChartLegend{Position: "left", ShowLegendKey: false}
plotArea := ChartPlotArea{
ShowBubbleSize: true,
ShowCatName: true,
ShowLeaderLines: false,
ShowPercent: true,
ShowSerName: true,
ShowVal: true,
}
assert.NoError(t, f.AddChart("Sheet1", "P1", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}))
assert.NoError(t, f.DeleteChart("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx")))
// Test delete chart with invalid sheet name
assert.EqualError(t, f.DeleteChart("Sheet:1", "P1"), ErrSheetNameInvalid.Error())
// Test delete chart on not exists worksheet
assert.EqualError(t, f.DeleteChart("SheetN", "A1"), "sheet SheetN does not exist")
// Test delete chart with invalid coordinates
assert.EqualError(t, f.DeleteChart("Sheet1", ""), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
// Test delete chart on no chart worksheet
assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1"))
assert.NoError(t, f.Close())
}
func TestChartWithLogarithmicBase(t *testing.T) {
// Create test workbook with data
f := NewFile()
sheet1 := f.GetSheetName(0)
categories := map[string]float64{
"A1": 1,
"A2": 2,
"A3": 3,
"A4": 4,
"A5": 5,
"A6": 6,
"A7": 7,
"A8": 8,
"A9": 9,
"A10": 10,
"B1": 0.1,
"B2": 1,
"B3": 2,
"B4": 3,
"B5": 20,
"B6": 30,
"B7": 100,
"B8": 500,
"B9": 700,
"B10": 5000,
}
for cell, v := range categories {
assert.NoError(t, f.SetCellValue(sheet1, cell, v))
}
series := []ChartSeries{{Name: "value", Categories: "Sheet1!$A$1:$A$19", Values: "Sheet1!$B$1:$B$10"}}
dimension := []uint{640, 480, 320, 240}
for _, c := range []struct {
cell string
opts *Chart
}{
{cell: "C1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "Line chart without log scaling"}}}},
{cell: "M1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 10.5 scaling"}}, YAxis: ChartAxis{LogBase: 10.5}}},
{cell: "A25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1.9 scaling"}}, YAxis: ChartAxis{LogBase: 1.9}}},
{cell: "F25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 2 scaling"}}, YAxis: ChartAxis{LogBase: 2}}},
{cell: "K25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1000.1 scaling"}}, YAxis: ChartAxis{LogBase: 1000.1}}},
{cell: "P25", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[2], Height: dimension[3]}, Series: series, Title: []RichTextRun{{Text: "Line chart with log 1000 scaling"}}, YAxis: ChartAxis{LogBase: 1000}}},
} {
// Add two chart, one without and one with log scaling
assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts))
}
// Export workbook for human confirmation
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))
// Write the workbook to a buffer
var buffer bytes.Buffer
assert.NoError(t, f.Write(&buffer))
// Read back the workbook from the buffer
newFile, err := OpenReader(&buffer)
assert.NoError(t, err)
// Check the number of charts
expectedChartsCount := 6
chartsNum := newFile.countCharts()
if !assert.Equal(t, expectedChartsCount, chartsNum,
"Expected %d charts, actual %d", expectedChartsCount, chartsNum) {
t.FailNow()
}
chartSpaces := make([]xlsxChartSpace, expectedChartsCount)
type xmlChartContent []byte
xmlCharts := make([]xmlChartContent, expectedChartsCount)
expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000}
var (
drawingML interface{}
ok bool
)
for i := 0; i < expectedChartsCount; i++ {
chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
if drawingML, ok = newFile.Pkg.Load(chartPath); ok {
xmlCharts[i] = drawingML.([]byte)
}
assert.True(t, ok, "Can't open the %s", chartPath)
err = xml.Unmarshal(xmlCharts[i], &chartSpaces[i])
if !assert.NoError(t, err) {
t.FailNow()
}
chartLogBasePtr := chartSpaces[i].Chart.PlotArea.ValAx[0].Scaling.LogBase
if expectedChartsLogBase[i] == 0 {
if !assert.Nil(t, chartLogBasePtr, "LogBase is not nil") {
t.FailNow()
}
} else {
if !assert.NotNil(t, chartLogBasePtr, "LogBase is nil") {
t.FailNow()
}
if !assert.Equal(t, expectedChartsLogBase[i], *(chartLogBasePtr.Val),
"Expected log base to %f, actual %f", expectedChartsLogBase[i], *(chartLogBasePtr.Val)) {
t.FailNow()
}
}
}
}
================================================
FILE: col.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"strconv"
"strings"
"unicode"
"github.com/tiendc/go-deepcopy"
"golang.org/x/text/width"
)
// Cols defines an iterator to a sheet
type Cols struct {
err error
curCol, totalCols, totalRows, stashCol int
rawCellValue bool
sheet string
f *File
sheetXML []byte
sst *xlsxSST
}
// GetCols gets the value of all cells by columns on the worksheet based on the
// given worksheet name, returned as a two-dimensional array, where the value
// of the cell is converted to the `string` type. If the cell format can be
// applied to the value of the cell, the applied value will be used, otherwise
// the original value will be used.
//
// For example, get and traverse the value of all cells by columns on a
// worksheet named
// 'Sheet1':
//
// cols, err := f.GetCols("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for _, col := range cols {
// for _, rowCell := range col {
// fmt.Print(rowCell, "\t")
// }
// fmt.Println()
// }
func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) {
cols, err := f.Cols(sheet)
if err != nil {
return nil, err
}
results := make([][]string, 0, 64)
for cols.Next() {
col, _ := cols.Rows(opts...)
results = append(results, col)
}
return results, nil
}
// Next will return true if the next column is found.
func (cols *Cols) Next() bool {
cols.curCol++
return cols.curCol <= cols.totalCols
}
// Error will return an error when the error occurs.
func (cols *Cols) Error() error {
return cols.err
}
// Rows return the current column's row values.
func (cols *Cols) Rows(opts ...Options) ([]string, error) {
var rowIterator rowXMLIterator
if cols.stashCol >= cols.curCol {
return rowIterator.cells, rowIterator.err
}
cols.rawCellValue = cols.f.getOptions(opts...).RawCellValue
if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
rowIterator.inElement = xmlElement.Name.Local
if rowIterator.inElement == "row" {
rowIterator.cellCol = 0
rowIterator.cellRow++
attrR, _ := attrValToInt("r", xmlElement.Attr)
if attrR != 0 {
rowIterator.cellRow = attrR
}
}
if cols.rowXMLHandler(&rowIterator, &xmlElement, decoder); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
return rowIterator.cells, rowIterator.err
}
}
}
return rowIterator.cells, rowIterator.err
}
// columnXMLIterator defined runtime use field for the worksheet column SAX parser.
type columnXMLIterator struct {
err error
cols Cols
cellCol, curRow, row int
}
// columnXMLHandler parse the column XML element of the worksheet.
func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartElement) {
colIterator.err = nil
inElement := xmlElement.Name.Local
if inElement == "row" {
colIterator.row++
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "r" {
if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil {
return
}
colIterator.row = colIterator.curRow
}
}
colIterator.cols.totalRows = colIterator.row
colIterator.cellCol = 0
}
if inElement == "c" {
colIterator.cellCol++
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "r" {
if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil {
return
}
}
}
if colIterator.cellCol > colIterator.cols.totalCols {
colIterator.cols.totalCols = colIterator.cellCol
}
}
}
// rowXMLHandler parse the row XML element of the worksheet.
func (cols *Cols) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, decoder *xml.Decoder) {
if rowIterator.inElement == "c" {
rowIterator.cellCol++
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "r" {
if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil {
return
}
}
}
blank := rowIterator.cellRow - len(rowIterator.cells)
for i := 1; i < blank; i++ {
rowIterator.cells = append(rowIterator.cells, "")
}
if rowIterator.cellCol == cols.curCol {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, xmlElement)
val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue)
rowIterator.cells = append(rowIterator.cells, val)
}
}
}
// Cols returns a columns iterator, used for streaming reading data for a
// worksheet with a large data. This function is concurrency safe. For
// example:
//
// cols, err := f.Cols("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for cols.Next() {
// col, err := cols.Rows()
// if err != nil {
// fmt.Println(err)
// }
// for _, rowCell := range col {
// fmt.Print(rowCell, "\t")
// }
// fmt.Println()
// }
func (f *File) Cols(sheet string) (*Cols, error) {
if err := checkSheetName(sheet); err != nil {
return nil, err
}
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
ws := worksheet.(*xlsxWorksheet)
ws.mu.Lock()
defer ws.mu.Unlock()
output, _ := xml.Marshal(ws)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var colIterator columnXMLIterator
colIterator.cols.sheetXML = f.readBytes(name)
decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
columnXMLHandler(&colIterator, &xmlElement)
if colIterator.err != nil {
return &colIterator.cols, colIterator.err
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
colIterator.cols.f = f
colIterator.cols.sheet = sheet
return &colIterator.cols, nil
}
}
}
return &colIterator.cols, nil
}
// GetColVisible provides a function to get visible of a single column by given
// worksheet name and column name. This function is concurrency safe. For
// example, get visible state of column D in Sheet1:
//
// visible, err := f.GetColVisible("Sheet1", "D")
func (f *File) GetColVisible(sheet, col string) (bool, error) {
colNum, err := ColumnNameToNumber(col)
if err != nil {
return true, err
}
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return false, err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.Cols == nil {
return true, err
}
visible := true
for c := range ws.Cols.Col {
colData := &ws.Cols.Col[c]
if colData.Min <= colNum && colNum <= colData.Max {
visible = !colData.Hidden
}
}
return visible, err
}
// SetColVisible provides a function to set visible columns by given worksheet
// name, columns range and visibility. This function is concurrency safe.
//
// For example hide column D on Sheet1:
//
// err := f.SetColVisible("Sheet1", "D", false)
//
// Hide the columns from D to F (included):
//
// err := f.SetColVisible("Sheet1", "D:F", false)
func (f *File) SetColVisible(sheet, columns string, visible bool) error {
minVal, maxVal, err := f.parseColRange(columns)
if err != nil {
return err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock()
defer ws.mu.Unlock()
ws.setColVisible(minVal, maxVal, visible)
return err
}
// setColVisible provides a function to set the visibility of a single column
// or multiple columns by given column number.
func (ws *xlsxWorksheet) setColVisible(minVal, maxVal int, visible bool) {
colData := xlsxCol{
Min: minVal,
Max: maxVal,
Width: float64Ptr(defaultColWidth),
Hidden: !visible,
CustomWidth: true,
}
if ws.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, colData)
ws.Cols = &cols
return
}
ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Style = c.Style
fc.Width = c.Width
return fc
})
}
// GetColOutlineLevel provides a function to get outline level of a single
// column by given worksheet name and column name. For example, get outline
// level of column D in Sheet1:
//
// level, err := f.GetColOutlineLevel("Sheet1", "D")
func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
level := uint8(0)
colNum, err := ColumnNameToNumber(col)
if err != nil {
return level, err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return 0, err
}
if ws.Cols == nil {
return level, err
}
for c := range ws.Cols.Col {
colData := &ws.Cols.Col[c]
if colData.Min <= colNum && colNum <= colData.Max {
level = colData.OutlineLevel
}
}
return level, err
}
// parseColRange parse and convert column range with column name to the column number.
func (f *File) parseColRange(columns string) (minVal, maxVal int, err error) {
colsTab := strings.Split(columns, ":")
minVal, err = ColumnNameToNumber(colsTab[0])
if err != nil {
return
}
maxVal = minVal
if len(colsTab) == 2 {
if maxVal, err = ColumnNameToNumber(colsTab[1]); err != nil {
return
}
}
if maxVal < minVal {
minVal, maxVal = maxVal, minVal
}
return
}
// SetColOutlineLevel provides a function to set outline level of a single
// column by given worksheet name and column name. The value of parameter
// 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2:
//
// err := f.SetColOutlineLevel("Sheet1", "D", 2)
func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
if level > 7 || level < 1 {
return ErrOutlineLevel
}
colNum, err := ColumnNameToNumber(col)
if err != nil {
return err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.setColOutlineLevel(colNum, level)
return err
}
// setColOutlineLevel provides a function to set the outline level of a single
// column by given column number.
func (ws *xlsxWorksheet) setColOutlineLevel(colNum int, level uint8) {
colData := xlsxCol{
Min: colNum,
Max: colNum,
OutlineLevel: level,
CustomWidth: true,
}
if ws.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, colData)
ws.Cols = &cols
return
}
ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.Hidden = c.Hidden
fc.Phonetic = c.Phonetic
fc.Style = c.Style
fc.Width = c.Width
return fc
})
}
// SetColStyle provides a function to set style of columns by given worksheet
// name, columns range and style ID. This function is concurrency safe. Note
// that this will overwrite the existing styles for the columns, it won't
// append or merge style with existing styles.
//
// For example set style of column H on Sheet1:
//
// err = f.SetColStyle("Sheet1", "H", style)
//
// Set style of columns C:F on Sheet1:
//
// err = f.SetColStyle("Sheet1", "C:F", style)
func (f *File) SetColStyle(sheet, columns string, styleID int) error {
minVal, maxVal, err := f.parseColRange(columns)
if err != nil {
return err
}
f.mu.Lock()
s, err := f.stylesReader()
if err != nil {
f.mu.Unlock()
return err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
s.mu.Lock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
s.mu.Unlock()
return newInvalidStyleID(styleID)
}
s.mu.Unlock()
ws.mu.Lock()
ws.setColStyle(minVal, maxVal, styleID)
ws.mu.Unlock()
if rows := len(ws.SheetData.Row); rows > 0 {
for col := minVal; col <= maxVal; col++ {
from, _ := CoordinatesToCellName(col, 1)
to, _ := CoordinatesToCellName(col, rows)
err = f.SetCellStyle(sheet, from, to, styleID)
}
}
return err
}
// setColStyle provides a function to set the style of a single column or
// multiple columns.
func (ws *xlsxWorksheet) setColStyle(minVal, maxVal, styleID int) {
if ws.Cols == nil {
ws.Cols = &xlsxCols{}
}
width := defaultColWidth
if ws.SheetFormatPr != nil && ws.SheetFormatPr.DefaultColWidth > 0 {
width = ws.SheetFormatPr.DefaultColWidth
}
ws.Cols.Col = flatCols(xlsxCol{
Min: minVal,
Max: maxVal,
Width: float64Ptr(width),
Style: styleID,
}, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.CustomWidth = c.CustomWidth
fc.Hidden = c.Hidden
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Width = c.Width
return fc
})
}
// SetColWidth provides a function to set the width of a single column or
// multiple columns. This function is concurrency safe. For example:
//
// err := f.SetColWidth("Sheet1", "A", "H", 20)
func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error {
minVal, maxVal, err := f.parseColRange(startCol + ":" + endCol)
if err != nil {
return err
}
if width > MaxColumnWidth {
return ErrColumnWidth
}
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
ws.setColWidth(minVal, maxVal, width)
return err
}
// setColWidth provides a function to set the width of a single column or
// multiple columns.
func (ws *xlsxWorksheet) setColWidth(minVal, maxVal int, width float64) {
col := xlsxCol{
Min: minVal,
Max: maxVal,
Width: float64Ptr(width),
CustomWidth: true,
}
if ws.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, col)
ws.Cols = &cols
return
}
ws.Cols.Col = flatCols(col, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
fc.BestFit = c.BestFit
fc.Collapsed = c.Collapsed
fc.Hidden = c.Hidden
fc.OutlineLevel = c.OutlineLevel
fc.Phonetic = c.Phonetic
fc.Style = c.Style
return fc
})
}
// flatCols provides a method for the column's operation functions to flatten
// and check the worksheet columns.
func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol {
var fc []xlsxCol
for i := col.Min; i <= col.Max; i++ {
var c xlsxCol
_ = deepcopy.Copy(&c, col)
c.Min, c.Max = i, i
fc = append(fc, c)
}
inFlat := func(colID int, cols []xlsxCol) (int, bool) {
for idx, c := range cols {
if c.Max == colID && c.Min == colID {
return idx, true
}
}
return -1, false
}
for _, column := range cols {
for i := column.Min; i <= column.Max; i++ {
if idx, ok := inFlat(i, fc); ok {
fc[idx] = replacer(fc[idx], column)
continue
}
var c xlsxCol
_ = deepcopy.Copy(&c, column)
c.Min, c.Max = i, i
fc = append(fc, c)
}
}
return fc
}
// positionObjectPixels calculate the vertices that define the position of a
// graphical object within the worksheet in pixels.
//
// +------------+------------+
// | A | B |
// +-----+------------+------------+
// | |(x1,y1) | |
// | 1 |(A1)._______|______ |
// | | | | |
// | | | | |
// +-----+----| OBJECT |-----+
// | | | | |
// | 2 | |______________. |
// | | | (B2)|
// | | | (x2,y2)|
// +-----+------------+------------+
//
// Example of an object that covers some range reference from cell A1 to B2.
//
// Based on the width and height of the object we need to calculate 8 vars:
//
// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2.
//
// We also calculate the absolute x and y position of the top left vertex of
// the object. This is required for images.
//
// The width and height of the cells that the object occupies can be
// variable and have to be taken into account.
//
// The values of col_start and row_start are passed in from the calling
// function. The values of col_end and row_end are calculated by
// subtracting the width and height of the object from the width and
// height of the underlying cells.
//
// colStart # Col containing upper left corner of object.
// x1 # Distance to left side of object.
//
// rowStart # Row containing top left corner of object.
// y1 # Distance to top of object.
//
// colEnd # Col containing lower right corner of object.
// x2 # Distance to right side of object.
//
// rowEnd # Row containing bottom right corner of object.
// y2 # Distance to bottom of object.
//
// width # Width of object frame.
// height # Height of object frame.
func (f *File) positionObjectPixels(sheet string, col, row, width, height int, opts *GraphicOptions) (int, int, int, int, int, int, int, int) {
colIdx, rowIdx := col-1, row-1
// Initialized end cell to the same as the start cell.
colEnd, rowEnd := colIdx, rowIdx
x1, y1, x2, y2 := opts.OffsetX, opts.OffsetY, width, height
if opts.Positioning != "oneCell" {
// Using a twoCellAnchor, the maximum possible offset is limited by the
// "from" cell dimensions. If these were to be exceeded the "toPoint" would
// be calculated incorrectly, since the requested "fromPoint" is not possible
x1 = min(x1, f.getColWidth(sheet, col))
y1 = min(y1, f.getRowHeight(sheet, row))
x2 += x1
y2 += y1
// Subtract the underlying cell widths to find end cell of the object.
for x2 >= f.getColWidth(sheet, colEnd+1) {
colEnd++
x2 -= f.getColWidth(sheet, colEnd)
}
// Subtract the underlying cell heights to find end cell of the object.
for y2 >= f.getRowHeight(sheet, rowEnd+1) {
rowEnd++
y2 -= f.getRowHeight(sheet, rowEnd)
}
}
// The end vertices are whatever is left from the width and height.
return colIdx, rowIdx, colEnd, rowEnd, x1, y1, x2, y2
}
// getColWidth provides a function to get column width in pixels by given
// sheet name and column number.
func (f *File) getColWidth(sheet string, col int) int {
ws, _ := f.workSheetReader(sheet)
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.Cols != nil {
width := -1.0
for _, v := range ws.Cols.Col {
if v.Min <= col && col <= v.Max && v.Width != nil {
width = *v.Width
break
}
}
if width != -1.0 {
return int(convertColWidthToPixels(width))
}
}
if ws.SheetFormatPr != nil {
if ws.SheetFormatPr.DefaultColWidth > 0 {
return int(convertColWidthToPixels(ws.SheetFormatPr.DefaultColWidth))
}
if ws.SheetFormatPr.BaseColWidth > 0 {
return int(convertColWidthToPixels(float64(ws.SheetFormatPr.BaseColWidth))) + 5
}
}
// Optimization for when the column widths haven't changed.
return int(defaultColWidthPixels)
}
// GetColStyle provides a function to get column style ID by given worksheet
// name and column name. This function is concurrency safe.
func (f *File) GetColStyle(sheet, col string) (int, error) {
var styleID int
colNum, err := ColumnNameToNumber(col)
if err != nil {
return styleID, err
}
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return styleID, err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.Cols != nil {
for _, v := range ws.Cols.Col {
if v.Min <= colNum && colNum <= v.Max {
styleID = v.Style
}
}
}
return styleID, err
}
// GetColWidth provides a function to get column width by given worksheet name
// and column name. This function is concurrency safe.
func (f *File) GetColWidth(sheet, col string) (float64, error) {
colNum, err := ColumnNameToNumber(col)
if err != nil {
return defaultColWidth, err
}
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return defaultColWidth, err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.Cols != nil {
var width float64
for _, v := range ws.Cols.Col {
if v.Min <= colNum && colNum <= v.Max && v.Width != nil {
width = *v.Width
}
}
if width != 0 {
return width, err
}
}
if ws.SheetFormatPr != nil {
if ws.SheetFormatPr.DefaultColWidth > 0 {
return ws.SheetFormatPr.DefaultColWidth, err
}
if ws.SheetFormatPr.BaseColWidth > 0 {
return float64(ws.SheetFormatPr.BaseColWidth), err
}
}
// Optimization for when the column widths haven't changed.
return defaultColWidth, err
}
// InsertCols provides a function to insert new columns before the given column
// name and number of columns. For example, create two columns before column
// C in Sheet1:
//
// err := f.InsertCols("Sheet1", "C", 2)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) InsertCols(sheet, col string, n int) error {
num, err := ColumnNameToNumber(col)
if err != nil {
return err
}
if n < 1 || n > MaxColumns {
return ErrColumnNumber
}
return f.adjustHelper(sheet, columns, num, n)
}
// RemoveCol provides a function to remove single column by given worksheet
// name and column index. For example, remove column C in Sheet1:
//
// err := f.RemoveCol("Sheet1", "C")
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) RemoveCol(sheet, col string) error {
num, err := ColumnNameToNumber(col)
if err != nil {
return err
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.formulaSI.Clear()
for rowIdx := range ws.SheetData.Row {
rowData := &ws.SheetData.Row[rowIdx]
for colIdx := range rowData.C {
colName, _, _ := SplitCellName(rowData.C[colIdx].R)
if colName == col {
rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)[:len(rowData.C)-1]
break
}
}
}
return f.adjustHelper(sheet, columns, num, -1)
}
// convertColWidthToPixels provides function to convert the width of a cell
// from user's units to pixels. Excel rounds the column width to the nearest
// pixel. If the width hasn't been set by the user we use the default value.
// If the column is hidden it has a value of zero.
func convertColWidthToPixels(width float64) float64 {
var pixels float64
var maxDigitWidth float64 = 8
if width == 0 {
return pixels
}
pixels = (width*maxDigitWidth + 0.5)
return float64(int(pixels))
}
// calcTextWidth calculates the column width needed to display a text based on
// the font family, font size, font weight and italic format.
func (fnt *Font) calcTextWidth(text string) float64 {
var lowerUnits, upperUnits, wideUnits float64
for _, r := range text {
switch width.LookupRune(r).Kind() {
case width.EastAsianWide, width.EastAsianFullwidth:
wideUnits += 2
default:
if unicode.IsUpper(r) {
upperUnits++
continue
}
lowerUnits++
}
}
sz := fnt.Size
if sz <= 0 {
sz = defaultFontSize
}
lowerFactor, upperFactor, wideFactor := 1.0, 1.0, 1.0
if fw, ok := supportedFontWidthFactors[strings.ToLower(fnt.Family)]; ok {
lowerFactor, upperFactor, wideFactor = fw[0], fw[1], fw[2]
}
w := (lowerUnits*lowerFactor + upperUnits*upperFactor + wideUnits*wideFactor) * (sz / defaultFontSize)
if fnt.Bold {
w *= 1.05
}
if fnt.Italic {
w *= 1.05
}
if inStrSlice(supportedVertAlignTypes, fnt.VertAlign, true) != -1 {
w *= 0.6
}
return w
}
// calcRichTextWidth calculates the column width needed to display a rich text
// based on the font format.
func (fnt *Font) calcRichTextWidth(runs []RichTextRun) float64 {
var w, width float64
for _, run := range runs {
if run.Font != nil {
fnt = run.Font
}
if i := strings.IndexAny(run.Text, "\r\n"); i >= 0 {
first := run.Text[:i]
rest := run.Text[i+1:]
if w += fnt.calcTextWidth(first); w > width {
width = w
}
w = fnt.calcTextWidth(rest)
continue
}
w += fnt.calcTextWidth(run.Text)
}
if w > width {
width = w
}
return width
}
// autoFitColWidth provides a function to auto fit columns width according to
// their text content with default font size and font.
func (f *File) autoFitColWidth(sheet string, col, rows int, defaultFnt *Font) (float64, error) {
var width float64
for row := 1; row <= rows; row++ {
cell, err := CoordinatesToCellName(col, row)
if err != nil {
return width, err
}
val, err := f.CalcCellValue(sheet, cell)
if err != nil && inStrSlice([]string{
formulaErrorDIV,
formulaErrorNAME,
formulaErrorNA,
formulaErrorNUM,
formulaErrorVALUE,
formulaErrorREF,
formulaErrorNULL,
formulaErrorSPILL,
formulaErrorCALC,
formulaErrorGETTINGDATA,
}, err.Error(), true) != -1 {
val = err.Error()
}
if val == "" {
continue
}
styleID, _ := f.GetCellStyle(sheet, cell)
fnt := defaultFnt
style, err := f.GetStyle(styleID)
if err != nil {
return width, err
}
if style != nil && style.Font != nil {
fnt = style.Font
}
if cellType, _ := f.GetCellType(sheet, cell); cellType == CellTypeInlineString || cellType == CellTypeSharedString {
runs, _ := f.GetCellRichText(sheet, cell)
if w := fnt.calcRichTextWidth(runs); w > width {
width = w
}
continue
}
if w := fnt.calcTextWidth(val); w > width {
width = w
}
}
return width, nil
}
// AutoFitColWidth provides a function to auto fit columns width according to
// their text content with font format. If the selected range contains hidden
// columns and those columns have content, this function will unhide the hidden
// columns. Not that this function calculates the width of the text
// approximately based on the font format, currently does not support merged
// cells. the actual width may be different when you open the workbook in Office
// applications. This process can be relatively slow on large worksheets, so
// this should normally only be called once per column, at the end of your
// processing.
//
// For example, auto fit column width for column D on Sheet1:
//
// err := f.AutoFitColWidth("Sheet1", "D")
//
// Auto fit column width for columns D to F on Sheet1:
//
// err := f.AutoFitColWidth("Sheet1", "D:F")
func (f *File) AutoFitColWidth(sheet, columns string) error {
minVal, maxVal, err := f.parseColRange(columns)
if err != nil {
return err
}
rows, err := f.GetRows(sheet)
if err != nil {
return err
}
defaultFnt := &Font{}
font, err := f.readDefaultFont()
if err != nil {
return err
}
if font != nil {
if font.Sz != nil && font.Sz.Val != nil {
defaultFnt.Size = *font.Sz.Val
}
if font.Name != nil && font.Name.Val != nil {
defaultFnt.Family = *font.Name.Val
}
}
ws, _ := f.workSheetReader(sheet)
for col := minVal; col <= maxVal; col++ {
if width, _ := f.autoFitColWidth(sheet, col, len(rows), defaultFnt); width > 0 {
width += 2
if width > MaxColumnWidth {
width = MaxColumnWidth
}
ws.setColVisible(col, col, true)
ws.setColWidth(col, col, width)
}
}
return err
}
================================================
FILE: col_test.go
================================================
package excelize
import (
"fmt"
"path/filepath"
"sort"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCols(t *testing.T) {
const sheet2 = "Sheet2"
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
cols, err := f.Cols(sheet2)
if !assert.NoError(t, err) {
t.FailNow()
}
var collectedRows [][]string
for cols.Next() {
rows, err := cols.Rows()
assert.NoError(t, err)
collectedRows = append(collectedRows, trimSliceSpace(rows))
}
if !assert.NoError(t, cols.Error()) {
t.FailNow()
}
returnedColumns, err := f.GetCols(sheet2)
assert.NoError(t, err)
for i := range returnedColumns {
returnedColumns[i] = trimSliceSpace(returnedColumns[i])
}
if !assert.Equal(t, collectedRows, returnedColumns) {
t.FailNow()
}
assert.NoError(t, f.Close())
f = NewFile()
cells := []string{"C2", "C3", "C4"}
for _, cell := range cells {
assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
}
_, err = f.Rows("Sheet1")
assert.NoError(t, err)
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
Dimension: &xlsxDimension{
Ref: "C2:C4",
},
})
_, err = f.Rows("Sheet1")
assert.NoError(t, err)
// Test columns iterator with invalid sheet name
_, err = f.Cols("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test get columns cells with invalid sheet name
_, err = f.GetCols("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test columns iterator with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
cols, err = f.Cols("Sheet1")
assert.NoError(t, err)
cols.Next()
_, err = cols.Rows()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
f.checked = sync.Map{}
_, err = f.Cols("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
_, err = f.Cols("Sheet1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
func TestColumnsIterator(t *testing.T) {
sheetName, colCount, expectedNumCol := "Sheet2", 0, 9
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)
cols, err := f.Cols(sheetName)
require.NoError(t, err)
for cols.Next() {
colCount++
require.True(t, colCount <= expectedNumCol, "colCount is greater than expected")
}
assert.Equal(t, expectedNumCol, colCount)
assert.NoError(t, f.Close())
f, sheetName, colCount, expectedNumCol = NewFile(), "Sheet1", 0, 4
cells := []string{"C2", "C3", "C4", "D2", "D3", "D4"}
for _, cell := range cells {
assert.NoError(t, f.SetCellValue(sheetName, cell, 1))
}
cols, err = f.Cols(sheetName)
require.NoError(t, err)
for cols.Next() {
colCount++
require.True(t, colCount <= 4, "colCount is greater than expected")
}
assert.Equal(t, expectedNumCol, colCount)
}
func TestColsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.Cols("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
}
func TestGetColsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.GetCols("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`B
`, NameSpaceSpreadSheet.Value)))
f.checked = sync.Map{}
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`B
`, NameSpaceSpreadSheet.Value)))
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
f = NewFile()
cols, err := f.Cols("Sheet1")
assert.NoError(t, err)
cols.totalRows = 2
cols.totalCols = 2
cols.curCol = 1
cols.sheetXML = []byte(fmt.Sprintf(`A
`, NameSpaceSpreadSheet.Value))
_, err = cols.Rows()
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
f.Sheet.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Cols("Sheet1")
assert.NoError(t, err)
}
func TestColsRows(t *testing.T) {
f := NewFile()
_, err := f.Cols("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
Dimension: &xlsxDimension{
Ref: "A1:A1",
},
})
f = NewFile()
f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Cols("Sheet1")
if !assert.NoError(t, err) {
t.FailNow()
}
f = NewFile()
cols, err := f.Cols("Sheet1")
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = cols.Rows()
assert.NoError(t, err)
cols.stashCol, cols.curCol = 0, 1
// Test if token is nil
cols.sheetXML = nil
_, err = cols.Rows()
assert.NoError(t, err)
}
func TestColumnVisibility(t *testing.T) {
t.Run("TestBook1", func(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
// Hide/display a column with SetColVisible
assert.NoError(t, f.SetColVisible("Sheet1", "F", false))
assert.NoError(t, f.SetColVisible("Sheet1", "F", true))
visible, err := f.GetColVisible("Sheet1", "F")
assert.Equal(t, true, visible)
assert.NoError(t, err)
// Test hiding a few columns SetColVisible(...false)...
assert.NoError(t, f.SetColVisible("Sheet1", "F:V", false))
visible, err = f.GetColVisible("Sheet1", "F")
assert.Equal(t, false, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "U")
assert.Equal(t, false, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "V")
assert.Equal(t, false, visible)
assert.NoError(t, err)
// ...and displaying them back SetColVisible(...true)
assert.NoError(t, f.SetColVisible("Sheet1", "V:F", true))
visible, err = f.GetColVisible("Sheet1", "F")
assert.Equal(t, true, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "U")
assert.Equal(t, true, visible)
assert.NoError(t, err)
visible, err = f.GetColVisible("Sheet1", "G")
assert.Equal(t, true, visible)
assert.NoError(t, err)
// Test get column visible on not exists worksheet
_, err = f.GetColVisible("SheetN", "F")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get column visible with invalid sheet name
_, err = f.GetColVisible("Sheet:1", "F")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test get column visible with illegal cell reference
_, err = f.GetColVisible("Sheet1", "*")
assert.EqualError(t, err, newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColVisible("Sheet1", "*", false), newInvalidColumnNameError("*").Error())
// Test set column visible with invalid sheet name
assert.EqualError(t, f.SetColVisible("Sheet:1", "A", false), ErrSheetNameInvalid.Error())
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetColVisible("Sheet3", "E", false))
assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), newInvalidColumnNameError("-1").Error())
assert.EqualError(t, f.SetColVisible("SheetN", "E", false), "sheet SheetN does not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColumnVisibility.xlsx")))
})
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
assert.NoError(t, err)
visible, err := f.GetColVisible("Sheet1", "B")
assert.Equal(t, true, visible)
assert.NoError(t, err)
})
}
func TestOutlineLevel(t *testing.T) {
f := NewFile()
level, err := f.GetColOutlineLevel("Sheet1", "D")
assert.Equal(t, uint8(0), level)
assert.NoError(t, err)
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4))
level, err = f.GetColOutlineLevel("Sheet1", "D")
assert.Equal(t, uint8(4), level)
assert.NoError(t, err)
level, err = f.GetColOutlineLevel("SheetN", "A")
assert.Equal(t, uint8(0), level)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test column outline level with invalid sheet name
_, err = f.GetColOutlineLevel("Sheet:1", "A")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.NoError(t, f.SetColWidth("Sheet2", "A", "D", 13))
assert.EqualError(t, f.SetColWidth("Sheet2", "A", "D", MaxColumnWidth+1), ErrColumnWidth.Error())
// Test set column width with invalid sheet name
assert.EqualError(t, f.SetColWidth("Sheet:1", "A", "D", 13), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2))
assert.NoError(t, f.SetRowOutlineLevel("Sheet1", 2, 7))
assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "D", 8), ErrOutlineLevel.Error())
assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 2, 8), ErrOutlineLevel.Error())
// Test set row outline level on not exists worksheet
assert.EqualError(t, f.SetRowOutlineLevel("SheetN", 1, 4), "sheet SheetN does not exist")
// Test set row outline level with invalid sheet name
assert.EqualError(t, f.SetRowOutlineLevel("Sheet:1", 1, 4), ErrSheetNameInvalid.Error())
// Test get row outline level on not exists worksheet
_, err = f.GetRowOutlineLevel("SheetN", 1)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get row outline level with invalid sheet name
_, err = f.GetRowOutlineLevel("Sheet:1", 1)
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test set and get column outline level with illegal cell reference
assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "*", 1), newInvalidColumnNameError("*").Error())
_, err = f.GetColOutlineLevel("Sheet1", "*")
assert.EqualError(t, err, newInvalidColumnNameError("*").Error())
// Test set column outline level on not exists worksheet
assert.EqualError(t, f.SetColOutlineLevel("SheetN", "E", 2), "sheet SheetN does not exist")
assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 0, 1), newInvalidRowNumberError(0).Error())
level, err = f.GetRowOutlineLevel("Sheet1", 2)
assert.NoError(t, err)
assert.Equal(t, uint8(7), level)
_, err = f.GetRowOutlineLevel("Sheet1", 0)
assert.EqualError(t, err, newInvalidRowNumberError(0).Error())
level, err = f.GetRowOutlineLevel("Sheet1", 10)
assert.NoError(t, err)
assert.Equal(t, uint8(0), level)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx")))
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2))
assert.NoError(t, f.Close())
}
func TestSetColStyle(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello"))
styleID, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"94D3A2"}, Pattern: 1}})
assert.NoError(t, err)
// Test set column style on not exists worksheet
assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN does not exist")
// Test set column style with illegal column name
assert.EqualError(t, f.SetColStyle("Sheet1", "*", styleID), newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColStyle("Sheet1", "A:*", styleID), newInvalidColumnNameError("*").Error())
// Test set column style with invalid style ID
assert.EqualError(t, f.SetColStyle("Sheet1", "B", -1), newInvalidStyleID(-1).Error())
// Test set column style with not exists style ID
assert.EqualError(t, f.SetColStyle("Sheet1", "B", 10), newInvalidStyleID(10).Error())
// Test set column style with invalid sheet name
assert.EqualError(t, f.SetColStyle("Sheet:1", "A", 0), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID))
style, err := f.GetColStyle("Sheet1", "B")
assert.NoError(t, err)
assert.Equal(t, styleID, style)
// Test set column style with already exists column with style
assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID))
assert.NoError(t, f.SetColStyle("Sheet1", "D:C", styleID))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[1].C[2].S = 0
cellStyleID, err := f.GetCellStyle("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, styleID, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx")))
// Test set column style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetColStyle("Sheet1", "C:F", styleID), "XML syntax error on line 1: invalid UTF-8")
// Test set column style with worksheet properties columns default width settings
f = NewFile()
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{DefaultColWidth: float64Ptr(20)}))
style, err = f.NewStyle(&Style{Alignment: &Alignment{Vertical: "center"}})
assert.NoError(t, err)
assert.NoError(t, f.SetColStyle("Sheet1", "A:Z", style))
width, err := f.GetColWidth("Sheet1", "B")
assert.NoError(t, err)
assert.Equal(t, 20.0, width)
}
func TestColWidth(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetColWidth("Sheet1", "B", "A", 12))
assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 12))
width, err := f.GetColWidth("Sheet1", "A")
assert.Equal(t, float64(12), width)
assert.NoError(t, err)
width, err = f.GetColWidth("Sheet1", "C")
assert.Equal(t, defaultColWidth, width)
assert.NoError(t, err)
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetFormatPr = &xlsxSheetFormatPr{DefaultColWidth: 10}
ws.(*xlsxWorksheet).Cols = nil
width, err = f.GetColWidth("Sheet1", "A")
assert.NoError(t, err)
assert.Equal(t, 10.0, width)
assert.Equal(t, 80, f.getColWidth("Sheet1", 1))
// Test set and get column width with illegal cell reference
width, err = f.GetColWidth("Sheet1", "*")
assert.Equal(t, defaultColWidth, width)
assert.EqualError(t, err, newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColWidth("Sheet1", "*", "B", 1), newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColWidth("Sheet1", "A", "*", 1), newInvalidColumnNameError("*").Error())
// Test set column width on not exists worksheet
assert.EqualError(t, f.SetColWidth("SheetN", "B", "A", 12), "sheet SheetN does not exist")
// Test get column width on not exists worksheet
_, err = f.GetColWidth("SheetN", "A")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get column width invalid sheet name
_, err = f.GetColWidth("Sheet:1", "A")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestColWidth.xlsx")))
convertRowHeightToPixels(0)
}
func TestGetColStyle(t *testing.T) {
f := NewFile()
styleID, err := f.GetColStyle("Sheet1", "A")
assert.NoError(t, err)
assert.Equal(t, styleID, 0)
// Test get column style on not exists worksheet
_, err = f.GetColStyle("SheetN", "A")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get column style with illegal column name
_, err = f.GetColStyle("Sheet1", "*")
assert.EqualError(t, err, newInvalidColumnNameError("*").Error())
// Test get column style with invalid sheet name
_, err = f.GetColStyle("Sheet:1", "A")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
}
func TestInsertCols(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
assert.NoError(t, fillCells(f, sheet1, 10, 10))
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3"))
assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", []AutoFilterOptions{{Column: "B", Expression: "x != blanks"}}))
assert.NoError(t, f.InsertCols(sheet1, "A", 1))
// Test insert column with illegal cell reference
assert.EqualError(t, f.InsertCols(sheet1, "*", 1), newInvalidColumnNameError("*").Error())
// Test insert column with invalid sheet name
assert.EqualError(t, f.InsertCols("Sheet:1", "A", 1), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.InsertCols(sheet1, "A", 0), ErrColumnNumber.Error())
assert.EqualError(t, f.InsertCols(sheet1, "A", MaxColumns), ErrColumnNumber.Error())
assert.EqualError(t, f.InsertCols(sheet1, "A", MaxColumns-10), ErrColumnNumber.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertCols.xlsx")))
}
func TestRemoveCol(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
assert.NoError(t, fillCells(f, sheet1, 10, 15))
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.SetCellHyperLink(sheet1, "C5", "https://github.com", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "B1"))
assert.NoError(t, f.MergeCell(sheet1, "A2", "B2"))
assert.NoError(t, f.RemoveCol(sheet1, "A"))
assert.NoError(t, f.RemoveCol(sheet1, "A"))
// Test remove column with illegal cell reference
assert.EqualError(t, f.RemoveCol("Sheet1", "*"), newInvalidColumnNameError("*").Error())
// Test remove column on not exists worksheet
assert.EqualError(t, f.RemoveCol("SheetN", "B"), "sheet SheetN does not exist")
// Test remove column with invalid sheet name
assert.EqualError(t, f.RemoveCol("Sheet:1", "A"), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx")))
}
func TestConvertColWidthToPixels(t *testing.T) {
assert.Equal(t, -7.0, convertColWidthToPixels(-1))
}
func TestAutoFitColWidth(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetColVisible("Sheet1", "A:C", false))
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "SUBSTITUTE(\"1 \",\" \",\"\")"))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "A:B"))
assert.NoError(t, f.SetCellValue("Sheet1", "C1", 1234567890))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "C"))
visible, err := f.GetColVisible("Sheet1", "A")
assert.NoError(t, err)
assert.True(t, visible)
visible, err = f.GetColVisible("Sheet1", "B")
assert.NoError(t, err)
assert.False(t, visible)
visible, err = f.GetColVisible("Sheet1", "C")
assert.NoError(t, err)
assert.True(t, visible)
styleID, err := f.NewStyle(&Style{Font: &Font{Family: "Microsoft YaHei", Bold: true, Italic: true, Charset: intPtr(134)}})
assert.NoError(t, err)
assert.NoError(t, f.SetColStyle("Sheet1", "D", styleID))
assert.NoError(t, f.SetCellValue("Sheet1", "D1", 1234567890))
assert.NoError(t, f.SetCellValue("Sheet1", "D2", "文本1234567890"))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "D"))
assert.NoError(t, f.SetCellValue("Sheet1", "E1", "text"))
assert.NoError(t, f.SetCellValue("Sheet1", "E2", "文本********"))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "E"))
styleID, err = f.NewStyle(&Style{Font: &Font{Size: 50}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "F1", "F1", styleID))
assert.NoError(t, f.SetCellValue("Sheet1", "F1", "Text"))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "F"))
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "G1", "1/0"))
assert.NoError(t, f.SetCellFormula("Sheet1", "G2", "1^\"text\""))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "G:G"))
names := make([]string, 0, len(supportedFontWidthFactors))
for name := range supportedFontWidthFactors {
names = append(names, name)
}
sort.Strings(names)
for idx, name := range names {
col, err := ColumnNumberToName(idx + 8)
assert.NoError(t, err)
styleID, err := f.NewStyle(&Style{Font: &Font{Family: name}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", col+"1", name))
assert.NoError(t, f.SetCellValue("Sheet1", col+"2", strings.ToUpper(name)))
assert.NoError(t, f.SetCellStyle("Sheet1", col+"1", col+"2", styleID))
}
assert.NoError(t, f.AutoFitColWidth("Sheet1", "H:KP"))
assert.NoError(t, f.SetCellValue("Sheet1", "KQ1", strings.Repeat("s", TotalCellChars)))
assert.NoError(t, f.AutoFitColWidth("Sheet1", "KQ"))
assert.NoError(t, err)
width, err := f.GetColWidth("Sheet1", "KQ")
assert.NoError(t, err)
assert.Equal(t, float64(MaxColumnWidth), width)
assert.Equal(t, f.AutoFitColWidth("Sheet1", ""), newInvalidColumnNameError(""))
assert.Equal(t, f.AutoFitColWidth("SheetN", "A"), ErrSheetNotExist{"SheetN"})
_, err = f.autoFitColWidth("Sheet1", TotalRows, 1, &Font{})
assert.Equal(t, ErrColumnNumber, err)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAutoFitColWidth.xlsx")))
f = NewFile()
// Test auto fit column width with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AutoFitColWidth("Sheet1", "A"), "XML syntax error on line 1: invalid UTF-8")
// Test auto fit column width with invalid cell style ID
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
SheetData: xlsxSheetData{Row: []xlsxRow{
{R: 1, C: []xlsxC{{R: "A1", S: 1, V: "Test"}}},
}},
})
_, err = f.autoFitColWidth("Sheet1", 1, 1, &Font{})
assert.Equal(t, err, newInvalidStyleID(1))
}
================================================
FILE: crypt.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"encoding/xml"
"hash"
"math"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/richardlehane/mscfb"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
"golang.org/x/text/encoding/unicode"
)
var (
blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption
oleIdentifier = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
headerCLSID = make([]byte, 16)
difSect = -4
endOfChain = -2
fatSect = -3
iterCount = 50000
packageEncryptionChunkSize = 4096
packageOffset = 8 // First 8 bytes are the size of the stream
sheetProtectionSpinCount = 1e5
workbookProtectionSpinCount = 1e5
)
// Encryption specifies the encryption structure, streams, and storages are
// required when encrypting ECMA-376 documents.
type Encryption struct {
XMLName xml.Name `xml:"encryption"`
KeyData KeyData `xml:"keyData"`
DataIntegrity DataIntegrity `xml:"dataIntegrity"`
KeyEncryptors KeyEncryptors `xml:"keyEncryptors"`
}
// KeyData specifies the cryptographic attributes used to encrypt the data.
type KeyData struct {
SaltSize int `xml:"saltSize,attr"`
BlockSize int `xml:"blockSize,attr"`
KeyBits int `xml:"keyBits,attr"`
HashSize int `xml:"hashSize,attr"`
CipherAlgorithm string `xml:"cipherAlgorithm,attr"`
CipherChaining string `xml:"cipherChaining,attr"`
HashAlgorithm string `xml:"hashAlgorithm,attr"`
SaltValue string `xml:"saltValue,attr"`
}
// DataIntegrity specifies the encrypted copies of the salt and hash values
// used to help ensure that the integrity of the encrypted data has not been
// compromised.
type DataIntegrity struct {
EncryptedHmacKey string `xml:"encryptedHmacKey,attr"`
EncryptedHmacValue string `xml:"encryptedHmacValue,attr"`
}
// KeyEncryptors specifies the key encryptors used to encrypt the data.
type KeyEncryptors struct {
KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"`
}
// KeyEncryptor specifies that the schema used by this encryptor is the schema
// specified for password-based encryptors.
type KeyEncryptor struct {
XMLName xml.Name `xml:"keyEncryptor"`
URI string `xml:"uri,attr"`
EncryptedKey EncryptedKey `xml:"encryptedKey"`
}
// EncryptedKey used to generate the encrypting key.
type EncryptedKey struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"`
SpinCount int `xml:"spinCount,attr"`
EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"`
EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"`
EncryptedKeyValue string `xml:"encryptedKeyValue,attr"`
KeyData
}
// StandardEncryptionHeader structure is used by ECMA-376 document encryption
// [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify
// encryption properties for an encrypted stream.
type StandardEncryptionHeader struct {
Flags uint32
SizeExtra uint32
AlgID uint32
AlgIDHash uint32
KeySize uint32
ProviderType uint32
Reserved1 uint32
Reserved2 uint32
CspName string
}
// StandardEncryptionVerifier structure is used by Office Binary Document RC4
// CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this
// structure MUST specify the hashing algorithm and encryption algorithm used
// in the EncryptionVerifier structure.
type StandardEncryptionVerifier struct {
SaltSize uint32
Salt []byte
EncryptedVerifier []byte
VerifierHashSize uint32
EncryptedVerifierHash []byte
}
// encryptionInfo structure is used for standard encryption with SHA1
// cryptographic algorithm.
type encryption struct {
BlockSize, SaltSize int
EncryptedKeyValue, EncryptedVerifierHashInput, EncryptedVerifierHashValue, SaltValue []byte
KeyBits uint32
}
// Decrypt API decrypts the CFB file format with ECMA-376 agile encryption and
// standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160,
// SHA1, SHA256, SHA384 and SHA512 currently.
func Decrypt(raw []byte, opts *Options) (packageBuf []byte, err error) {
doc, err := mscfb.New(bytes.NewReader(raw))
if err != nil {
return
}
var encryptionInfoBuf, encryptedPackageBuf []byte
if encryptionInfoBuf, encryptedPackageBuf, err = extractPart(doc); err != nil {
return
}
mechanism, err := encryptionMechanism(encryptionInfoBuf)
if err != nil || mechanism == "extensible" {
return
}
if mechanism == "agile" {
return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opts)
}
return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opts)
}
// Encrypt API encrypt data with the password.
func Encrypt(raw []byte, opts *Options) ([]byte, error) {
encryptor := encryption{
EncryptedVerifierHashInput: make([]byte, 16),
EncryptedVerifierHashValue: make([]byte, 32),
SaltValue: make([]byte, 16),
BlockSize: 16,
KeyBits: 128,
SaltSize: 16,
}
// Key Encryption
encryptionInfoBuffer, err := encryptor.standardKeyEncryption(opts.Password)
if err != nil {
return nil, err
}
// Package Encryption
encryptedPackage := make([]byte, 8)
binary.LittleEndian.PutUint64(encryptedPackage, uint64(len(raw)))
encryptedPackage = append(encryptedPackage, encryptor.encrypt(raw)...)
// Create a new CFB
compoundFile := &cfb{
paths: []string{"Root Entry/"},
sectors: []sector{{name: "Root Entry", typeID: 5}},
}
compoundFile.put("EncryptionInfo", encryptionInfoBuffer)
compoundFile.put("EncryptedPackage", encryptedPackage)
return compoundFile.write(), nil
}
// extractPart extract data from storage by specified part name.
func extractPart(doc *mscfb.Reader) ([]byte, []byte, error) {
var encryptionInfoBuf, encryptedPackageBuf []byte
for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
switch entry.Name {
case "EncryptionInfo":
buf := make([]byte, entry.Size)
if _, err := doc.Read(buf); err != nil {
return encryptionInfoBuf, encryptedPackageBuf, err
}
encryptionInfoBuf = buf
case "EncryptedPackage":
buf := make([]byte, entry.Size)
if _, err := doc.Read(buf); err != nil {
return encryptionInfoBuf, encryptedPackageBuf, err
}
encryptedPackageBuf = buf
}
}
return encryptionInfoBuf, encryptedPackageBuf, nil
}
// encryptionMechanism parse password-protected documents created mechanism.
func encryptionMechanism(buffer []byte) (mechanism string, err error) {
if len(buffer) < 4 {
err = ErrUnknownEncryptMechanism
return
}
versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4])
if versionMajor == 4 && versionMinor == 4 {
mechanism = "agile"
return
} else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 {
mechanism = "standard"
return
} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
mechanism = "extensible"
}
err = ErrUnsupportedEncryptMechanism
return
}
// ECMA-376 Standard Encryption
// standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption.
func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opts *Options) ([]byte, error) {
encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12])
block := encryptionInfoBuf[12 : 12+encryptionHeaderSize]
header := StandardEncryptionHeader{
Flags: binary.LittleEndian.Uint32(block[:4]),
SizeExtra: binary.LittleEndian.Uint32(block[4:8]),
AlgID: binary.LittleEndian.Uint32(block[8:12]),
AlgIDHash: binary.LittleEndian.Uint32(block[12:16]),
KeySize: binary.LittleEndian.Uint32(block[16:20]),
ProviderType: binary.LittleEndian.Uint32(block[20:24]),
Reserved1: binary.LittleEndian.Uint32(block[24:28]),
Reserved2: binary.LittleEndian.Uint32(block[28:32]),
CspName: string(block[32:]),
}
block = encryptionInfoBuf[12+encryptionHeaderSize:]
algIDMap := map[uint32]string{
0x0000660E: "AES-128",
0x0000660F: "AES-192",
0x00006610: "AES-256",
}
algorithm := "AES"
_, ok := algIDMap[header.AlgID]
if !ok {
algorithm = "RC4"
}
verifier := standardEncryptionVerifier(algorithm, block)
secretKey, err := standardConvertPasswdToKey(header, verifier, opts)
if err != nil {
return nil, err
}
// decrypted data
x := encryptedPackageBuf[8:]
blob, err := aes.NewCipher(secretKey)
if err != nil {
return nil, err
}
decrypted := make([]byte, len(x))
size := 16
for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size {
blob.Decrypt(decrypted[bs:be], x[bs:be])
}
return decrypted, err
}
// standardEncryptionVerifier extract ECMA-376 standard encryption verifier.
func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier {
verifier := StandardEncryptionVerifier{
SaltSize: binary.LittleEndian.Uint32(blob[:4]),
Salt: blob[4:20],
EncryptedVerifier: blob[20:36],
VerifierHashSize: binary.LittleEndian.Uint32(blob[36:40]),
}
switch algorithm {
case "RC4":
verifier.EncryptedVerifierHash = blob[40:60]
case "AES":
verifier.EncryptedVerifierHash = blob[40:72]
}
return verifier
}
// standardConvertPasswdToKey generate intermediate key from given password.
func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opts *Options) ([]byte, error) {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
passwordBuffer, err := encoder.Bytes([]byte(opts.Password))
if err != nil {
return nil, err
}
key := hashing("sha1", verifier.Salt, passwordBuffer)
for i := 0; i < iterCount; i++ {
iterator := createUInt32LEBuffer(i, 4)
key = hashing("sha1", iterator, key)
}
var block int
hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
cbRequiredKeyLength := int(header.KeySize) / 8
cbHash := sha1.Size
buf1 := bytes.Repeat([]byte{0x36}, 64)
buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...)
x1 := hashing("sha1", buf1)
buf2 := bytes.Repeat([]byte{0x5c}, 64)
buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...)
x2 := hashing("sha1", buf2)
x3 := append(x1, x2...)
keyDerived := x3[:cbRequiredKeyLength]
return keyDerived, err
}
// standardXORBytes perform XOR operations for two bytes slice.
func standardXORBytes(a, b []byte) []byte {
r := make([][2]byte, len(a))
for i, e := range a {
r[i] = [2]byte{e, b[i]}
}
buf := make([]byte, len(a))
for p, q := range r {
buf[p] = q[0] ^ q[1]
}
return buf
}
// encrypt provides a function to encrypt given value with AES cryptographic
// algorithm.
func (e *encryption) encrypt(input []byte) []byte {
inputBytes := len(input)
if pad := inputBytes % e.BlockSize; pad != 0 {
inputBytes += e.BlockSize - pad
}
var output, chunk []byte
encryptedChunk := make([]byte, e.BlockSize)
for i := 0; i < inputBytes; i += e.BlockSize {
if i+e.BlockSize <= len(input) {
chunk = input[i : i+e.BlockSize]
} else {
chunk = input[i:]
}
chunk = append(chunk, make([]byte, e.BlockSize-len(chunk))...)
c, _ := aes.NewCipher(e.EncryptedKeyValue)
c.Encrypt(encryptedChunk, chunk)
output = append(output, encryptedChunk...)
}
return output
}
// standardKeyEncryption encrypt convert the password to an encryption key.
func (e *encryption) standardKeyEncryption(password string) ([]byte, error) {
if countUTF16String(password) == 0 || countUTF16String(password) > MaxFieldLength {
return nil, ErrPasswordLengthInvalid
}
var storage cfb
storage.writeUint16(0x0003)
storage.writeUint16(0x0002)
storage.writeUint32(0x24)
storage.writeUint32(0xA4)
storage.writeUint32(0x24)
storage.writeUint32(0x00)
storage.writeUint32(0x660E)
storage.writeUint32(0x8004)
storage.writeUint32(0x80)
storage.writeUint32(0x18)
storage.writeUint64(0x00)
providerName := "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
storage.writeStrings(providerName)
storage.writeUint16(0x00)
storage.writeUint32(0x10)
keyDataSaltValue, _ := randomBytes(16)
verifierHashInput, _ := randomBytes(16)
e.SaltValue = keyDataSaltValue
e.EncryptedKeyValue, _ = standardConvertPasswdToKey(
StandardEncryptionHeader{KeySize: e.KeyBits},
StandardEncryptionVerifier{Salt: e.SaltValue},
&Options{Password: password})
verifierHashInputKey := hashing("sha1", verifierHashInput)
e.EncryptedVerifierHashInput = e.encrypt(verifierHashInput)
e.EncryptedVerifierHashValue = e.encrypt(verifierHashInputKey)
storage.writeBytes(e.SaltValue)
storage.writeBytes(e.EncryptedVerifierHashInput)
storage.writeUint32(0x14)
storage.writeBytes(e.EncryptedVerifierHashValue)
storage.position = 0
return storage.stream, nil
}
// ECMA-376 Agile Encryption
// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256,
// SHA384 and SHA512.
func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opts *Options) (packageBuf []byte, err error) {
var encryptionInfo Encryption
if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
return
}
// Convert the password into an encryption key.
key, err := convertPasswdToKey(opts.Password, blockKey, encryptionInfo)
if err != nil {
return
}
// Use the key to decrypt the package key.
encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey
saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
if err != nil {
return
}
encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue)
if err != nil {
return
}
packageKey, _ := decrypt(key, saltValue, encryptedKeyValue)
// Use the package key to decrypt the package.
return decryptPackage(packageKey, encryptedPackageBuf, encryptionInfo)
}
// convertPasswdToKey convert the password into an encryption key.
func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) {
var b bytes.Buffer
saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue)
if err != nil {
return
}
b.Write(saltValue)
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
passwordBuffer, err := encoder.Bytes([]byte(passwd))
if err != nil {
return
}
b.Write(passwordBuffer)
// Generate the initial hash.
key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes())
// Now regenerate until spin count.
for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ {
iterator := createUInt32LEBuffer(i, 4)
key = hashing(encryption.KeyData.HashAlgorithm, iterator, key)
}
// Now generate the final hash.
key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey)
// Truncate or pad as needed to get to length of keyBits.
keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8
if len(key) < keyBytes {
tmp := make([]byte, 0x36)
key = append(key, tmp...)
} else if len(key) > keyBytes {
key = key[:keyBytes]
}
return
}
// hashing data by specified hash algorithm.
func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
hashMap := map[string]hash.Hash{
"md4": md4.New(),
"md5": md5.New(),
"ripemd-160": ripemd160.New(),
"sha1": sha1.New(),
"sha256": sha256.New(),
"sha384": sha512.New384(),
"sha512": sha512.New(),
}
handler, ok := hashMap[strings.ToLower(hashAlgorithm)]
if !ok {
return key
}
for _, buf := range buffer {
_, _ = handler.Write(buf)
}
key = handler.Sum(nil)
return key
}
// createUInt32LEBuffer create buffer with little endian 32-bit unsigned
// integer.
func createUInt32LEBuffer(value int, bufferSize int) []byte {
buf := make([]byte, bufferSize)
binary.LittleEndian.PutUint32(buf, uint32(value))
return buf
}
// parseEncryptionInfo parse the encryption info XML into an object.
func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) {
err = xml.Unmarshal(encryptionInfo, &encryption)
return
}
// decrypt provides a function to decrypt input by given cipher algorithm,
// cipher chaining, key and initialization vector.
func decrypt(key, iv, input []byte) (packageKey []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return input, err
}
cipher.NewCBCDecrypter(block, iv).CryptBlocks(input, input)
return input, nil
}
// decryptPackage decrypt package by given packageKey and encryption
// info.
func decryptPackage(packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
encryptedKey, offset := encryption.KeyData, packageOffset
var i, start, end int
var iv, outputChunk []byte
for end < len(input) {
start = end
end = start + packageEncryptionChunkSize
if end > len(input) {
end = len(input)
}
// Grab the next chunk
var inputChunk []byte
if (end + offset) < len(input) {
inputChunk = input[start+offset : end+offset]
} else {
inputChunk = input[start+offset : end]
}
// Pad the chunk if it is not an integer multiple of the block size
remainder := len(inputChunk) % encryptedKey.BlockSize
if remainder != 0 {
inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...)
}
// Create the initialization vector
iv, err = createIV(i, encryption)
if err != nil {
return
}
// Decrypt the chunk and add it to the array
outputChunk, err = decrypt(packageKey, iv, inputChunk)
if err != nil {
return
}
outputChunks = append(outputChunks, outputChunk...)
i++
}
return
}
// createIV create an initialization vector (IV).
func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
encryptedKey := encryption.KeyData
// Create the block key from the current index
var blockKeyBuf []byte
if reflect.TypeOf(blockKey).Kind() == reflect.Int {
blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4)
} else {
blockKeyBuf = blockKey.([]byte)
}
saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
if err != nil {
return nil, err
}
// Create the initialization vector by hashing the salt with the block key.
// Truncate or pad as needed to meet the block size.
iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...))
if len(iv) < encryptedKey.BlockSize {
tmp := make([]byte, 0x36)
iv = append(iv, tmp...)
} else if len(iv) > encryptedKey.BlockSize {
iv = iv[:encryptedKey.BlockSize]
}
return iv, nil
}
// randomBytes returns securely generated random bytes. It will return an
// error if the system's secure random number generator fails to function
// correctly, in which case the caller should not continue.
func randomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
return b, err
}
// ISO Write Protection Method
// genISOPasswdHash implements the ISO password hashing algorithm by given
// plaintext password, name of the cryptographic hash algorithm, salt value
// and spin count.
func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) {
if countUTF16String(passwd) < 1 || countUTF16String(passwd) > MaxFieldLength {
err = ErrPasswordLengthInvalid
return
}
algorithmName, ok := map[string]string{
"MD4": "md4",
"MD5": "md5",
"SHA-1": "sha1",
"SHA-256": "sha256",
"SHA-384": "sha384",
"SHA-512": "sha512",
}[hashAlgorithm]
if !ok {
err = ErrUnsupportedHashAlgorithm
return
}
var b bytes.Buffer
s, _ := randomBytes(16)
if salt != "" {
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
return
}
}
b.Write(s)
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
passwordBuffer, _ := encoder.Bytes([]byte(passwd))
b.Write(passwordBuffer)
// Generate the initial hash.
key := hashing(algorithmName, b.Bytes())
// Now regenerate until spin count.
for i := 0; i < spinCount; i++ {
iterator := createUInt32LEBuffer(i, 4)
key = hashing(algorithmName, key, iterator)
}
hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
return
}
// Compound File Binary Implements
// cfb structure is used for the compound file binary (CFB) file format writer.
type cfb struct {
stream []byte
position int
paths []string
sectors []sector
}
// sector structure used for FAT, directory, miniFAT, and miniStream sectors.
type sector struct {
clsID, content []byte
name string
C, L, R, color, size, start, state, typeID int
}
// writeBytes write bytes in the stream by a given value with an offset.
func (c *cfb) writeBytes(value []byte) {
pos := c.position
for i := 0; i < len(value); i++ {
for j := len(c.stream); j <= i+pos; j++ {
c.stream = append(c.stream, 0)
}
c.stream[i+pos] = value[i]
}
c.position = pos + len(value)
}
// writeUint16 write an uint16 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint16(value int) {
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(value))
c.writeBytes(buf)
}
// writeUint32 write an uint32 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint32(value int) {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(value))
c.writeBytes(buf)
}
// writeUint64 write an uint64 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint64(value int) {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(value))
c.writeBytes(buf)
}
// writeStrings write strings in the stream by a given value with an offset.
func (c *cfb) writeStrings(value string) {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
buffer, err := encoder.Bytes([]byte(value))
if err != nil {
return
}
c.writeBytes(buffer)
}
// put provides a function to add an entry to compound file by given entry name
// and raw bytes.
func (c *cfb) put(name string, content []byte) {
path := c.paths[0]
if len(path) <= len(name) && name[:len(path)] == path {
path = name
} else {
if len(path) > 0 && string(path[len(path)-1]) != "/" {
path += "/"
}
path = strings.ReplaceAll(path+name, "//", "/")
}
file := sector{name: path, typeID: 2, content: content, size: len(content)}
c.sectors = append(c.sectors, file)
c.paths = append(c.paths, path)
}
// compare provides a function to compare object path, each set of sibling
// objects in one level of the containment hierarchy (all child objects under
// a storage object) is represented as a red-black tree. The parent object of
// this set of siblings will have a pointer to the top of this tree.
func (c *cfb) compare(left, right string) int {
L, R, i, j := strings.Split(left, "/"), strings.Split(right, "/"), 0, 0
for Z := int(math.Min(float64(len(L)), float64(len(R)))); i < Z; i++ {
if j = len(L[i]) - len(R[i]); j != 0 {
return j
}
if L[i] != R[i] {
if L[i] < R[i] {
return -1
}
return 1
}
}
return len(L) - len(R)
}
// prepare provides a function to prepare object before write stream.
func (c *cfb) prepare() {
type object struct {
path string
sector sector
}
var objects []object
for i := 0; i < len(c.paths); i++ {
if c.sectors[i].typeID == 0 {
continue
}
objects = append(objects, object{path: c.paths[i], sector: c.sectors[i]})
}
sort.Slice(objects, func(i, j int) bool {
return c.compare(objects[i].path, objects[j].path) == 0
})
c.paths, c.sectors = []string{}, []sector{}
for i := 0; i < len(objects); i++ {
c.paths = append(c.paths, objects[i].path)
c.sectors = append(c.sectors, objects[i].sector)
}
for i := 0; i < len(objects); i++ {
sector, path := &c.sectors[i], c.paths[i]
sector.name, sector.color = filepath.Base(path), 1
sector.L, sector.R, sector.C = -1, -1, -1
sector.size, sector.start = len(sector.content), 0
if len(sector.clsID) == 0 {
sector.clsID = headerCLSID
}
if i == 0 {
sector.C = -1
if len(objects) > 1 {
sector.C = 1
}
sector.size, sector.typeID = 0, 5
} else {
if len(c.paths) > i+1 && filepath.Dir(c.paths[i+1]) == filepath.Dir(path) {
sector.R = i + 1
}
sector.typeID = 2
}
}
}
// locate provides a function to locate sectors location and size of the
// compound file.
func (c *cfb) locate() []int {
var miniStreamSectorSize, FATSectorSize int
for i := 0; i < len(c.sectors); i++ {
sector := c.sectors[i]
if len(sector.content) == 0 {
continue
}
size := len(sector.content)
if size > 0 {
if size < 0x1000 {
miniStreamSectorSize += (size + 0x3F) >> 6
} else {
FATSectorSize += (size + 0x01FF) >> 9
}
}
}
directorySectors := (len(c.paths) + 3) >> 2
miniStreamSectors := (miniStreamSectorSize + 7) >> 3
miniFATSectors := (miniStreamSectorSize + 0x7F) >> 7
sectors := miniStreamSectors + FATSectorSize + directorySectors + miniFATSectors
FATSectors := (sectors + 0x7F) >> 7
DIFATSectors := 0
if FATSectors > 109 {
DIFATSectors = int(math.Ceil((float64(FATSectors) - 109) / 0x7F))
}
for ((sectors + FATSectors + DIFATSectors + 0x7F) >> 7) > FATSectors {
FATSectors++
if FATSectors <= 109 {
DIFATSectors = 0
} else {
DIFATSectors = int(math.Ceil((float64(FATSectors) - 109) / 0x7F))
}
}
location := []int{1, DIFATSectors, FATSectors, miniFATSectors, directorySectors, FATSectorSize, miniStreamSectorSize, 0}
c.sectors[0].size = miniStreamSectorSize << 6
c.sectors[0].start = location[0] + location[1] + location[2] + location[3] + location[4] + location[5]
location[7] = c.sectors[0].start + ((location[6] + 7) >> 3)
return location
}
// writeMSAT provides a function to write compound file master sector allocation
// table.
func (c *cfb) writeMSAT(location []int) {
var i, offset int
for i = 0; i < 109; i++ {
if i < location[2] {
c.writeUint32(location[1] + i)
} else {
c.writeUint32(-1)
}
}
if location[1] != 0 {
for offset = 0; offset < location[1]; offset++ {
for ; i < 236+offset*127; i++ {
if i < location[2] {
c.writeUint32(location[1] + i)
} else {
c.writeUint32(-1)
}
}
if offset == location[1]-1 {
c.writeUint32(endOfChain)
} else {
c.writeUint32(offset + 1)
}
}
}
}
// writeDirectoryEntry provides a function to write compound file directory
// entries. The directory entry array is an array of directory entries that
// are grouped into a directory sector. Each storage object or stream object
// within a compound file is represented by a single directory entry. The
// space for the directory sectors that are holding the array is allocated
// from the FAT.
func (c *cfb) writeDirectoryEntry(location []int) {
var sector sector
var j, sectorSize int
for i := 0; i < location[4]<<2; i++ {
var path string
if i < len(c.paths) {
path = c.paths[i]
}
if i >= len(c.paths) || len(path) == 0 {
for j = 0; j < 17; j++ {
c.writeUint32(0)
}
for j = 0; j < 3; j++ {
c.writeUint32(-1)
}
for j = 0; j < 12; j++ {
c.writeUint32(0)
}
continue
}
sector = c.sectors[i]
if i == 0 {
if sector.size > 0 {
sector.start = sector.start - 1
} else {
sector.start = endOfChain
}
}
name := sector.name
sectorSize = 2 * (len(name) + 1)
c.writeStrings(name)
c.position += 64 - 2*(len(name))
c.writeUint16(sectorSize)
c.writeBytes([]byte(string(rune(sector.typeID))))
c.writeBytes([]byte(string(rune(sector.color))))
c.writeUint32(sector.L)
c.writeUint32(sector.R)
c.writeUint32(sector.C)
if len(sector.clsID) == 0 {
for j = 0; j < 4; j++ {
c.writeUint32(0)
}
} else {
c.writeBytes(sector.clsID)
}
c.writeUint32(sector.state)
c.writeUint32(0)
c.writeUint32(0)
c.writeUint32(0)
c.writeUint32(0)
c.writeUint32(sector.start)
c.writeUint32(sector.size)
c.writeUint32(0)
}
}
// writeSectorChains provides a function to write compound file sector chains.
func (c *cfb) writeSectorChains(location []int) sector {
var i, j, offset, sectorSize int
writeSectorChain := func(head, offset int) int {
for offset += head; i < offset-1; i++ {
c.writeUint32(i + 1)
}
if head != 0 {
i++
c.writeUint32(endOfChain)
}
return offset
}
for offset += location[1]; i < offset; i++ {
c.writeUint32(difSect)
}
for offset += location[2]; i < offset; i++ {
c.writeUint32(fatSect)
}
offset = writeSectorChain(location[3], offset)
offset = writeSectorChain(location[4], offset)
sector := c.sectors[0]
for ; j < len(c.sectors); j++ {
if sector = c.sectors[j]; len(sector.content) == 0 {
continue
}
if sectorSize = len(sector.content); sectorSize < 0x1000 {
continue
}
c.sectors[j].start = offset
offset = writeSectorChain((sectorSize+0x01FF)>>9, offset)
}
writeSectorChain((location[6]+7)>>3, offset)
for c.position&0x1FF != 0 {
c.writeUint32(endOfChain)
}
i, offset = 0, 0
for j = 0; j < len(c.sectors); j++ {
if sector = c.sectors[j]; len(sector.content) == 0 {
continue
}
if sectorSize = len(sector.content); sectorSize == 0 || sectorSize >= 0x1000 {
continue
}
sector.start = offset
offset = writeSectorChain((sectorSize+0x3F)>>6, offset)
}
for c.position&0x1FF != 0 {
c.writeUint32(endOfChain)
}
return sector
}
// write provides a function to create compound file package stream.
func (c *cfb) write() []byte {
c.prepare()
location := c.locate()
c.stream = make([]byte, location[7]<<9)
var i, j int
for i = 0; i < 8; i++ {
c.writeBytes([]byte{oleIdentifier[i]})
}
c.writeBytes(make([]byte, 16))
c.writeUint16(0x003E)
c.writeUint16(0x0003)
c.writeUint16(0xFFFE)
c.writeUint16(0x0009)
c.writeUint16(0x0006)
c.writeBytes(make([]byte, 10))
c.writeUint32(location[2])
c.writeUint32(location[0] + location[1] + location[2] + location[3] - 1)
c.writeUint32(0)
c.writeUint32(1 << 12)
if location[3] != 0 {
c.writeUint32(location[0] + location[1] + location[2] - 1)
} else {
c.writeUint32(endOfChain)
}
c.writeUint32(location[3])
if location[1] != 0 {
c.writeUint32(location[0] - 1)
} else {
c.writeUint32(endOfChain)
}
c.writeUint32(location[1])
c.writeMSAT(location)
sector := c.writeSectorChains(location)
c.writeDirectoryEntry(location)
for i = 1; i < len(c.sectors); i++ {
sector = c.sectors[i]
if sector.size >= 0x1000 {
c.position = (sector.start + 1) << 9
for j = 0; j < sector.size; j++ {
c.writeBytes([]byte{sector.content[j]})
}
for ; j&0x1FF != 0; j++ {
c.writeBytes([]byte{0})
}
}
}
for i = 1; i < len(c.sectors); i++ {
sector = c.sectors[i]
if sector.size > 0 && sector.size < 0x1000 {
for j = 0; j < sector.size; j++ {
c.writeBytes([]byte{sector.content[j]})
}
for ; j&0x3F != 0; j++ {
c.writeBytes([]byte{0})
}
}
}
for c.position < len(c.stream) {
c.writeBytes([]byte{0})
}
return c.stream
}
================================================
FILE: crypt_test.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/base64"
"encoding/binary"
"os"
"path/filepath"
"strings"
"testing"
"github.com/richardlehane/mscfb"
"github.com/stretchr/testify/assert"
)
func TestEncrypt(t *testing.T) {
// Test decrypt spreadsheet with incorrect password
_, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "passwd"})
assert.EqualError(t, err, ErrWorkbookPassword.Error())
// Test decrypt spreadsheet with password
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
assert.NoError(t, err)
cell, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "SECRET", cell)
assert.NoError(t, f.Close())
// Test decrypt spreadsheet with unsupported encrypt mechanism
raw, err := os.ReadFile(filepath.Join("test", "encryptAES.xlsx"))
assert.NoError(t, err)
raw[2050] = 3
_, err = Decrypt(raw, &Options{Password: "password"})
assert.Equal(t, ErrUnsupportedEncryptMechanism, err)
// Test encrypt spreadsheet with invalid password
assert.EqualError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: strings.Repeat("*", MaxFieldLength+1)}), ErrPasswordLengthInvalid.Error())
// Test encrypt spreadsheet with new password
assert.NoError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"}))
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"})
assert.NoError(t, err)
cell, err = f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "SECRET", cell)
// Test remove password by save workbook with options
assert.NoError(t, f.Save(Options{Password: ""}))
assert.NoError(t, f.Close())
doc, err := mscfb.New(bytes.NewReader(raw))
assert.NoError(t, err)
encryptionInfoBuf, encryptedPackageBuf, err := extractPart(doc)
assert.NoError(t, err)
binary.LittleEndian.PutUint64(encryptionInfoBuf[20:32], uint64(0))
_, err = standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, &Options{Password: "password"})
assert.NoError(t, err)
_, err = decrypt(nil, nil, nil)
assert.EqualError(t, err, "crypto/aes: invalid key size 0")
_, err = agileDecrypt(encryptionInfoBuf, MacintoshCyrillicCharset, &Options{Password: "password"})
assert.EqualError(t, err, "XML syntax error on line 1: invalid character entity &0 (no semicolon)")
_, err = convertPasswdToKey("password", nil, Encryption{
KeyEncryptors: KeyEncryptors{KeyEncryptor: []KeyEncryptor{
{EncryptedKey: EncryptedKey{KeyData: KeyData{SaltValue: "=="}}},
}},
})
assert.EqualError(t, err, "illegal base64 data at input byte 0")
_, err = createIV([]byte{0}, Encryption{KeyData: KeyData{SaltValue: "=="}})
assert.EqualError(t, err, "illegal base64 data at input byte 0")
// Test error handling for EncryptionInfo parse failure
compoundFile := &cfb{
paths: []string{"Root Entry/"},
sectors: []sector{{name: "Root Entry", typeID: 5}},
}
compoundFile.put("EncryptionInfo", []byte{})
_, err = OpenReader(bytes.NewReader(compoundFile.write()))
assert.Equal(t, ErrWorkbookFileFormat, err)
// Test error handling for EncryptedPackage parse failure
compoundFile = &cfb{
paths: []string{"Root Entry/"},
sectors: []sector{{name: "Root Entry", typeID: 5}},
}
encryptionInfo := make([]byte, 100)
binary.LittleEndian.PutUint16(encryptionInfo[:2], 4)
binary.LittleEndian.PutUint16(encryptionInfo[2:4], 4)
compoundFile.put("EncryptionInfo", encryptionInfo)
compoundFile.put("EncryptedPackage", []byte{})
_, err = OpenReader(bytes.NewReader(compoundFile.write()))
assert.Equal(t, ErrWorkbookFileFormat, err)
// Test createIV when iv length is less than block size
_, err = createIV(0, Encryption{
KeyData: KeyData{
HashAlgorithm: "md5",
BlockSize: 32,
SaltValue: base64.StdEncoding.EncodeToString([]byte("")),
},
})
assert.NoError(t, err)
// Test decryptPackage error with padding
input := make([]byte, 18)
binary.LittleEndian.PutUint64(input[:8], 10)
for i := 8; i < 18; i++ {
input[i] = byte(i)
}
_, err = decryptPackage(make([]byte, 32), input, Encryption{
KeyData: KeyData{
HashAlgorithm: "sha256",
BlockSize: 16,
SaltValue: base64.StdEncoding.EncodeToString([]byte("")),
},
})
assert.NoError(t, err)
// Test IV creation error with invalid salt
input = make([]byte, 4104)
binary.LittleEndian.PutUint64(input[:8], 4096)
_, err = decryptPackage(make([]byte, 32), input, Encryption{
KeyData: KeyData{
HashAlgorithm: "sha256",
BlockSize: 16,
SaltValue: "==",
},
})
assert.Error(t, err)
// Test decrypt error with invalid key
_, err = decryptPackage([]byte{}, input, Encryption{
KeyData: KeyData{
HashAlgorithm: "sha256",
BlockSize: 16,
SaltValue: base64.StdEncoding.EncodeToString([]byte("")),
},
})
assert.Error(t, err)
// Test put with path that is a prefix of name
compoundFile = &cfb{
paths: []string{"Root Entry/"},
sectors: []sector{{name: "Root Entry", typeID: 5}},
}
compoundFile.put("Root Entry/Test", []byte(""))
compoundFile = &cfb{
paths: []string{"Root"},
sectors: []sector{{name: "Root", typeID: 5}},
}
compoundFile.put("Test", []byte(""))
// Test compare function with different scenarios
compoundFile = &cfb{}
assert.Equal(t, -1, compoundFile.compare("Root/A", "Root/B"))
assert.Equal(t, 1, compoundFile.compare("Root/B", "Root/A"))
assert.Equal(t, -1, compoundFile.compare("Root", "Root/Child"))
// Test prepare with typeID == 0 sector
compoundFile = &cfb{
paths: []string{"Root Entry/", "Skip/", "Valid/"},
sectors: []sector{
{name: "Root Entry", typeID: 5},
{name: "Skip", typeID: 0},
{name: "Valid", typeID: 2},
},
}
compoundFile.prepare()
// Test locate with FATSectors incrementing but staying <= 109
compoundFile = &cfb{
paths: []string{"Root Entry/"},
sectors: []sector{{name: "Root Entry", typeID: 5, content: []byte{}}},
}
numFiles := 1533
for i := range numFiles {
name := strings.Repeat("F", i%50+1)
compoundFile.paths = append(compoundFile.paths, name+"/")
compoundFile.sectors = append(compoundFile.sectors, sector{
name: name,
typeID: 2,
content: make([]byte, 4096),
})
}
compoundFile.locate()
// Test writeDirectoryEntry with empty clsID
compoundFile = &cfb{
paths: []string{"Root Entry/", "File1/"},
sectors: []sector{
{name: "Root Entry", typeID: 5, content: []byte{}, clsID: headerCLSID},
{name: "File1", typeID: 2, content: []byte("test"), clsID: []byte{}},
},
}
compoundFile.stream = make([]byte, 10000)
compoundFile.writeDirectoryEntry([]int{1, 0, 1, 0, 1, 0, 0, 0})
}
func TestEncryptionMechanism(t *testing.T) {
mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
assert.Equal(t, mechanism, "extensible")
assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
_, err = encryptionMechanism([]byte{})
assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error())
}
func TestHashing(t *testing.T) {
assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []byte(nil))
}
func TestGenISOPasswdHash(t *testing.T) {
for hashAlgorithm, expected := range map[string][]string{
"MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="},
"MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="},
"SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="},
"SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="},
"SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="},
"SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="},
} {
hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount))
assert.NoError(t, err)
assert.Equal(t, expected[0], hashValue)
assert.Equal(t, expected[1], saltValue)
}
}
================================================
FILE: datavalidation.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"fmt"
"io"
"math"
"slices"
"strings"
)
// DataValidationType defined the type of data validation.
type DataValidationType byte
// Data validation types.
const (
_ DataValidationType = iota
DataValidationTypeNone
DataValidationTypeCustom
DataValidationTypeDate
DataValidationTypeDecimal
DataValidationTypeList
DataValidationTypeTextLength
DataValidationTypeTime
DataValidationTypeWhole
)
// DataValidationErrorStyle defined the style of data validation error alert.
type DataValidationErrorStyle byte
// Data validation error styles.
const (
_ DataValidationErrorStyle = iota
DataValidationErrorStyleStop
DataValidationErrorStyleWarning
DataValidationErrorStyleInformation
)
// Data validation error styles.
const (
styleStop = "stop"
styleWarning = "warning"
styleInformation = "information"
)
// DataValidationOperator operator enum.
type DataValidationOperator byte
// Data validation operators.
const (
_ DataValidationOperator = iota
DataValidationOperatorBetween
DataValidationOperatorEqual
DataValidationOperatorGreaterThan
DataValidationOperatorGreaterThanOrEqual
DataValidationOperatorLessThan
DataValidationOperatorLessThanOrEqual
DataValidationOperatorNotBetween
DataValidationOperatorNotEqual
)
var (
// formulaEscaper mimics the Excel escaping rules for data validation,
// which converts `"` to `""` instead of `"`.
formulaEscaper = strings.NewReplacer(
`&`, `&`,
`<`, `<`,
`>`, `>`,
)
formulaUnescaper = strings.NewReplacer(
`&`, `&`,
`<`, `<`,
`>`, `>`,
)
// dataValidationTypeMap defined supported data validation types.
dataValidationTypeMap = map[DataValidationType]string{
DataValidationTypeNone: "none",
DataValidationTypeCustom: "custom",
DataValidationTypeDate: "date",
DataValidationTypeDecimal: "decimal",
DataValidationTypeList: "list",
DataValidationTypeTextLength: "textLength",
DataValidationTypeTime: "time",
DataValidationTypeWhole: "whole",
}
// dataValidationOperatorMap defined supported data validation operators.
dataValidationOperatorMap = map[DataValidationOperator]string{
DataValidationOperatorBetween: "between",
DataValidationOperatorEqual: "equal",
DataValidationOperatorGreaterThan: "greaterThan",
DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual",
DataValidationOperatorLessThan: "lessThan",
DataValidationOperatorLessThanOrEqual: "lessThanOrEqual",
DataValidationOperatorNotBetween: "notBetween",
DataValidationOperatorNotEqual: "notEqual",
}
)
// NewDataValidation return data validation struct.
func NewDataValidation(allowBlank bool) *DataValidation {
return &DataValidation{
AllowBlank: allowBlank,
ShowErrorMessage: false,
ShowInputMessage: false,
}
}
// SetError set error notice.
func (dv *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) {
dv.Error = &msg
dv.ErrorTitle = &title
strStyle := styleStop
switch style {
case DataValidationErrorStyleStop:
strStyle = styleStop
case DataValidationErrorStyleWarning:
strStyle = styleWarning
case DataValidationErrorStyleInformation:
strStyle = styleInformation
}
dv.ShowErrorMessage = true
dv.ErrorStyle = &strStyle
}
// SetInput set prompt notice.
func (dv *DataValidation) SetInput(title, msg string) {
dv.ShowInputMessage = true
dv.PromptTitle = &title
dv.Prompt = &msg
}
// SetDropList data validation list. If you type the items into the data
// validation dialog box (a delimited list), the limit is 255 characters,
// including the separators. If your data validation list source formula is
// over the maximum length limit, please set the allowed values in the
// worksheet cells, and use the SetSqrefDropList function to set the reference
// for their cells.
func (dv *DataValidation) SetDropList(keys []string) error {
formula := strings.Join(keys, ",")
if MaxFieldLength < countUTF16String(formula) {
return ErrDataValidationFormulaLength
}
dv.Type = dataValidationTypeMap[DataValidationTypeList]
if strings.HasPrefix(formula, "=") {
dv.Formula1 = formulaEscaper.Replace(formula)
return nil
}
dv.Formula1 = fmt.Sprintf(`"%s"`, strings.NewReplacer(`"`, `""`).Replace(formulaEscaper.Replace(formula)))
return nil
}
// SetRange provides function to set data validation range in drop list, only
// accepts int, float64, string or []string data type formula argument.
func (dv *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error {
genFormula := func(val interface{}) (string, error) {
var formula string
switch v := val.(type) {
case int:
formula = fmt.Sprintf("%d", v)
case float64:
if math.Abs(v) > math.MaxFloat32 {
return formula, ErrDataValidationRange
}
formula = fmt.Sprintf("%.17g", v)
case string:
formula = v
default:
return formula, ErrParameterInvalid
}
return formula, nil
}
formula1, err := genFormula(f1)
if err != nil {
return err
}
formula2, err := genFormula(f2)
if err != nil {
return err
}
dv.Formula1, dv.Formula2 = formula1, formula2
dv.Type = dataValidationTypeMap[t]
dv.Operator = dataValidationOperatorMap[o]
return err
}
// SetSqrefDropList provides set data validation on a range with source
// reference range of the worksheet by given data validation object and
// worksheet name. The data validation object can be created by
// NewDataValidation function. There are limits to the number of items that
// will show in a data validation drop down list: The list can show up to show
// 32768 items from a list on the worksheet. If you need more items than that,
// you could create a dependent drop down list, broken down by category. For
// example, set data validation on Sheet1!A7:B8 with validation criteria source
// Sheet1!E1:E3 settings, create in-cell dropdown by allowing list source:
//
// dv := excelize.NewDataValidation(true)
// dv.Sqref = "A7:B8"
// dv.SetSqrefDropList("$E$1:$E$3")
// err := f.AddDataValidation("Sheet1", dv)
func (dv *DataValidation) SetSqrefDropList(sqref string) {
dv.Formula1 = sqref
dv.Type = dataValidationTypeMap[DataValidationTypeList]
}
// SetSqref provides function to set data validation range in drop list.
func (dv *DataValidation) SetSqref(sqref string) {
if dv.Sqref == "" {
dv.Sqref = sqref
return
}
dv.Sqref = fmt.Sprintf("%s %s", dv.Sqref, sqref)
}
// AddDataValidation provides set data validation on a range of the worksheet
// by given data validation object and worksheet name. This function is
// concurrency safe. The data validation object can be created by
// NewDataValidation function.
//
// Example 1, set data validation on Sheet1!A1:B2 with validation criteria
// settings, show error alert after invalid data is entered with "Stop" style
// and custom title "error body":
//
// dv := excelize.NewDataValidation(true)
// dv.Sqref = "A1:B2"
// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween)
// dv.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body")
// err := f.AddDataValidation("Sheet1", dv)
//
// Example 2, set data validation on Sheet1!A3:B4 with validation criteria
// settings, and show input message when cell is selected:
//
// dv = excelize.NewDataValidation(true)
// dv.Sqref = "A3:B4"
// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan)
// dv.SetInput("input title", "input body")
// err = f.AddDataValidation("Sheet1", dv)
//
// Example 3, set data validation on Sheet1!A5:B6 with validation criteria
// settings, create in-cell dropdown by allowing list source:
//
// dv = excelize.NewDataValidation(true)
// dv.Sqref = "A5:B6"
// dv.SetDropList([]string{"1", "2", "3"})
// err = f.AddDataValidation("Sheet1", dv)
func (f *File) AddDataValidation(sheet string, dv *DataValidation) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock()
defer ws.mu.Unlock()
if nil == ws.DataValidations {
ws.DataValidations = new(xlsxDataValidations)
}
dataValidation := &xlsxDataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
Type: dv.Type,
}
if dv.Formula1 != "" {
dataValidation.Formula1 = &xlsxInnerXML{Content: dv.Formula1}
}
if dv.Formula2 != "" {
dataValidation.Formula2 = &xlsxInnerXML{Content: dv.Formula2}
}
ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dataValidation)
ws.DataValidations.Count = len(ws.DataValidations.DataValidation)
return err
}
// GetDataValidations returns data validations list by given worksheet name.
func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return nil, err
}
var (
dataValidations []*DataValidation
decodeExtLst = new(decodeExtLst)
decodeDataValidations *xlsxDataValidations
ext *xlsxExt
)
if ws.DataValidations != nil {
dataValidations = append(dataValidations, getDataValidations(ws.DataValidations)...)
}
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return dataValidations, err
}
for _, ext = range decodeExtLst.Ext {
if ext.URI == ExtURIDataValidations {
decodeDataValidations = new(xlsxDataValidations)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations)
dataValidations = append(dataValidations, getDataValidations(decodeDataValidations)...)
}
}
}
return dataValidations, err
}
// getDataValidations returns data validations list by given worksheet data
// validations.
func getDataValidations(dvs *xlsxDataValidations) []*DataValidation {
if dvs == nil {
return nil
}
var dataValidations []*DataValidation
for _, dv := range dvs.DataValidation {
if dv == nil {
continue
}
dataValidation := &DataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
Type: dv.Type,
}
if dv.Formula1 != nil {
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
}
if dv.Formula2 != nil {
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
}
if dv.XMSqref != "" {
dataValidation.Sqref = dv.XMSqref
dataValidation.Formula1 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula1, ""), "")
dataValidation.Formula2 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula2, ""), "")
}
dataValidations = append(dataValidations, dataValidation)
}
return dataValidations
}
// DeleteDataValidation delete data validation by given worksheet name and
// reference sequence. This function is concurrency safe. All data validations
// in the worksheet will be deleted if not specify reference sequence parameter.
//
// Example 1, delete data validation on Sheet1!A1:B2:
//
// err := f.DeleteDataValidation("Sheet1", "A1:B2")
//
// Example 2, delete data validations on Sheet1 with multiple cell ranges
// A1:B2 and C1:C3 with reference sequence slice:
//
// err := f.DeleteDataValidation("Sheet1", []string{"A1:B2", "C1:C3"}...)
//
// Example 3, delete data validations on Sheet1 with multiple cell ranges
// A1:B2 and C1:C3 with blank separated reference sequence string, the result
// same as example 2:
//
// err := f.DeleteDataValidation("Sheet1", "A1:B2 C1:C3")
//
// Example 4, delete all data validations on Sheet1:
//
// err := f.DeleteDataValidation("Sheet1")
func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.DataValidations == nil && ws.ExtLst == nil {
return nil
}
if sqref == nil {
ws.DataValidations = nil
return nil
}
delCells, err := flatSqref(strings.Join(sqref, " "))
if err != nil {
return err
}
if ws.DataValidations != nil {
if err = f.deleteDataValidation(ws, delCells); err != nil {
return err
}
}
if ws.ExtLst != nil {
return f.deleteX14DataValidation(ws, delCells)
}
return nil
}
// deleteDataValidation deletes data validation by given worksheet and cell
// reference list.
func (f *File) deleteDataValidation(ws *xlsxWorksheet, delCells map[int][][]int) error {
dv := ws.DataValidations
var err error
for i := 0; i < len(dv.DataValidation); i++ {
if dv.DataValidation[i].Sqref, err = deleteCellsFromSqref(dv.DataValidation[i].Sqref, delCells); err != nil {
return err
}
if len(dv.DataValidation[i].Sqref) == 0 {
dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...)
i--
}
}
dv.Count = len(dv.DataValidation)
if dv.Count == 0 {
ws.DataValidations = nil
}
return nil
}
// deleteX14DataValidation deletes data validation in the extLst element by
// given worksheet and cell reference list.
func (f *File) deleteX14DataValidation(ws *xlsxWorksheet, delCells map[int][][]int) error {
var (
err error
decodeExtLst = new(decodeExtLst)
decodeDataValidations *xlsxDataValidations
x14DataValidations *xlsxX14DataValidations
)
if err := f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for i, ext := range decodeExtLst.Ext {
if ext.URI == ExtURIDataValidations {
decodeDataValidations = new(xlsxDataValidations)
x14DataValidations = new(xlsxX14DataValidations)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations)
x14DataValidations.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value
x14DataValidations.DisablePrompts = decodeDataValidations.DisablePrompts
x14DataValidations.XWindow = decodeDataValidations.XWindow
x14DataValidations.YWindow = decodeDataValidations.YWindow
for _, dv := range decodeDataValidations.DataValidation {
if dv.XMSqref, err = deleteCellsFromSqref(dv.XMSqref, delCells); err != nil {
return err
}
if len(dv.XMSqref) == 0 {
continue
}
x14DataValidations.DataValidation = append(x14DataValidations.DataValidation, &xlsxX14DataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
XMSqref: dv.XMSqref,
Type: dv.Type,
Formula1: dv.Formula1,
Formula2: dv.Formula2,
})
}
x14DataValidations.Count = len(x14DataValidations.DataValidation)
x14DataValidationsBytes, _ := xml.Marshal(x14DataValidations)
decodeExtLst.Ext[i] = &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: ExtURIDataValidations, Content: string(x14DataValidationsBytes),
}
if x14DataValidations.Count == 0 {
decodeExtLst.Ext = slices.Delete(decodeExtLst.Ext, i, i+1)
}
}
}
extLstBytes, err := xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
return err
}
// squashSqref generates cell reference sequence by given cells coordinates list.
func squashSqref(cells [][]int) []string {
if len(cells) == 1 {
cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1])
return []string{cell}
} else if len(cells) == 0 {
return []string{}
}
var refs []string
l, r := 0, 0
for i := 1; i < len(cells); i++ {
if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 {
ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...))
if l == r {
ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
}
refs = append(refs, ref)
l, r = i, i
} else {
r++
}
}
ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...))
if l == r {
ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
}
return append(refs, ref)
}
// deleteCellsFromSqref deletes cells from given sqref by delCells map and
// returns the updated sqref.
func deleteCellsFromSqref(sqref string, delCells map[int][][]int) (string, error) {
var (
ref string
applySqref []string
)
colCells, err := flatSqref(sqref)
if err != nil {
return ref, err
}
for col, cells := range delCells {
for _, cell := range cells {
idx := inCoordinates(colCells[col], cell)
if idx != -1 {
colCells[col] = append(colCells[col][:idx], colCells[col][idx+1:]...)
}
}
}
for _, col := range colCells {
applySqref = append(applySqref, squashSqref(col)...)
}
return strings.Join(applySqref, " "), err
}
// isFormulaDataValidation returns whether the data validation rule is a formula.
func (dv *xlsxInnerXML) isFormula() bool {
return dv != nil && (!strings.HasPrefix(dv.Content, """) || !strings.HasSuffix(dv.Content, """))
}
// unescapeDataValidationFormula returns unescaped data validation formula.
func unescapeDataValidationFormula(val string) string {
if strings.HasPrefix(val, "\"") { // Text detection
return strings.NewReplacer(`""`, `"`).Replace(formulaUnescaper.Replace(val))
}
return formulaUnescaper.Replace(val)
}
================================================
FILE: datavalidation_test.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"fmt"
"math"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDataValidation(t *testing.T) {
resultFile := filepath.Join("test", "TestDataValidation.xlsx")
f := NewFile()
dv := NewDataValidation(true)
dv.Sqref = "A1:B2"
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetError(DataValidationErrorStyleStop, "error title", "error body")
dv.SetError(DataValidationErrorStyleWarning, "error title", "error body")
dv.SetError(DataValidationErrorStyleInformation, "error title", "error body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
dataValidations, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dataValidations, 1)
assert.NoError(t, f.SaveAs(resultFile))
dv = NewDataValidation(true)
dv.Sqref = "A3:B4"
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dv.SetInput("input title", "input body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dataValidations, 2)
assert.NoError(t, f.SaveAs(resultFile))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet2", "A2", &[]interface{}{"B2", 1}))
assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3}))
dv = NewDataValidation(true)
dv.Sqref = "A1:B1"
assert.NoError(t, dv.SetRange("INDIRECT($A$2)", "INDIRECT($A$3)", DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetError(DataValidationErrorStyleStop, "error title", "error body")
assert.NoError(t, f.AddDataValidation("Sheet2", dv))
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dataValidations, 2)
dataValidations, err = f.GetDataValidations("Sheet2")
assert.NoError(t, err)
assert.Len(t, dataValidations, 1)
dv = NewDataValidation(true)
dv.Sqref = "A5:B6"
for _, listValid := range [][]string{
{"1", "2", "3"},
{"=A1"},
{strings.Repeat("&", MaxFieldLength)},
{strings.Repeat("\u4E00", MaxFieldLength)},
{strings.Repeat("\U0001F600", 100), strings.Repeat("\u4E01", 50), "<&>"},
{`A<`, `B>`, `C"`, "D\t", `E'`, `F`},
} {
dv.Formula1 = ""
assert.NoError(t, dv.SetDropList(listValid),
"SetDropList failed for valid input %v", listValid)
assert.NotEmpty(t, dv.Formula1,
"Formula1 should not be empty for valid input %v", listValid)
}
assert.Equal(t, `"A<,B>,C"",D ,E',F"`, dv.Formula1)
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dataValidations, 3)
// Test get data validation on no exists worksheet
_, err = f.GetDataValidations("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get data validation with invalid sheet name
_, err = f.GetDataValidations("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(resultFile))
// Test get data validation on a worksheet without data validation settings
f = NewFile()
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []*DataValidation(nil), dataValidations)
// Test get data validations which storage in the extension lists
f = NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`Sheet1!$B$1:$B$5A7:B8`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []*DataValidation{
{
AllowBlank: true,
Type: "list",
Formula1: "Sheet1!$B$1:$B$5",
Sqref: "A7:B8",
},
}, dataValidations)
// Test get data validations with invalid extension list characters
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
_, err = f.GetDataValidations("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: element closed by ")
// Test get validations without validations
assert.Nil(t, getDataValidations(nil))
assert.Nil(t, getDataValidations(&xlsxDataValidations{DataValidation: []*xlsxDataValidation{nil}}))
}
func TestDataValidationError(t *testing.T) {
resultFile := filepath.Join("test", "TestDataValidationError.xlsx")
f := NewFile()
assert.NoError(t, f.SetCellStr("Sheet1", "E1", "E1"))
assert.NoError(t, f.SetCellStr("Sheet1", "E2", "E2"))
assert.NoError(t, f.SetCellStr("Sheet1", "E3", "E3"))
dv := NewDataValidation(true)
dv.SetSqref("A7:B8")
dv.SetSqref("A7:B8")
dv.SetSqrefDropList("$E$1:$E$3")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
dv = NewDataValidation(true)
err := dv.SetDropList(make([]string, 258))
if dv.Formula1 != "" {
t.Errorf("data validation error. Formula1 must be empty!")
return
}
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
assert.EqualError(t, dv.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
assert.EqualError(t, dv.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dv.SetSqref("A9:B10")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
// Test width invalid data validation formula
prevFormula1 := dv.Formula1
for _, keys := range [][]string{
make([]string, 257),
{strings.Repeat("s", 256)},
{strings.Repeat("\u4E00", 256)},
{strings.Repeat("\U0001F600", 128)},
{strings.Repeat("\U0001F600", 127), "s"},
} {
err = dv.SetDropList(keys)
assert.Equal(t, prevFormula1, dv.Formula1,
"Formula1 should be unchanged for invalid input %v", keys)
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
}
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, dv.SetRange(
-math.MaxFloat32, math.MaxFloat32,
DataValidationTypeWhole, DataValidationOperatorGreaterThan))
assert.EqualError(t, dv.SetRange(
-math.MaxFloat64, math.MaxFloat32,
DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error())
assert.EqualError(t, dv.SetRange(
math.SmallestNonzeroFloat64, math.MaxFloat64,
DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error())
assert.NoError(t, f.SaveAs(resultFile))
// Test add data validation on no exists worksheet
f = NewFile()
assert.EqualError(t, f.AddDataValidation("SheetN", nil), "sheet SheetN does not exist")
// Test add data validation with invalid sheet name
f = NewFile()
assert.EqualError(t, f.AddDataValidation("Sheet:1", nil), ErrSheetNameInvalid.Error())
}
func TestDeleteDataValidation(t *testing.T) {
f := NewFile()
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2"))
dv := NewDataValidation(true)
dv.Sqref = "A1:B2"
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetInput("input title", "input body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2"))
dv.Sqref = "A1"
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1"))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1"))
dv.Sqref = "C2:C5"
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "C4"))
dv = NewDataValidation(true)
dv.Sqref = "D2:D2 D3 D4"
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
dv.SetInput("input title", "input body")
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.DeleteDataValidation("Sheet1", "D3"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteDataValidation.xlsx")))
dv.Sqref = "A"
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1:A"
assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:B2"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test delete data validation on no exists worksheet
assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN does not exist")
// Test delete all data validation with invalid sheet name
assert.EqualError(t, f.DeleteDataValidation("Sheet:1"), ErrSheetNameInvalid.Error())
// Test delete all data validations in the worksheet
assert.NoError(t, f.DeleteDataValidation("Sheet1"))
assert.Nil(t, ws.(*xlsxWorksheet).DataValidations)
t.Run("with_data_validation_in_extLst", func(t *testing.T) {
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil,
`A1:A2Sheet1!$A$2:$A$4B1:B2Sheet1!$B$2:$B$3`,
NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value,
ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value))
f.checked = sync.Map{}
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:A2"))
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dvs, 1)
assert.Equal(t, "B1:B2", dvs[0].Sqref)
assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1:B2"))
dvs, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Empty(t, dvs)
})
t.Run("with_invalid_data_validation_in_extLst", func(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.deleteX14DataValidation(&xlsxWorksheet{
ExtLst: &xlsxExtLst{Ext: ""},
}, nil), "XML syntax error on line 1: element closed by ")
})
t.Run("with_unordered_sqref", func(t *testing.T) {
// Test deleting data validation when sqref has unordered ranges
f := NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).DataValidations = &xlsxDataValidations{
Count: 1,
DataValidation: []*xlsxDataValidation{{
AllowBlank: true,
ShowInputMessage: true,
ShowErrorMessage: true,
Type: "whole",
Operator: "between",
Sqref: "A5:A10 A15:A20 A3:A4",
Formula1: &xlsxInnerXML{Content: "1"},
Formula2: &xlsxInnerXML{Content: "100"},
}},
}
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A7"))
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dvs, 1)
assert.Equal(t, "A3:A6 A8:A10 A15:A20", dvs[0].Sqref)
})
t.Run("with_unordered_sqref_in_extLst", func(t *testing.T) {
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil,
`A5:A10 A15:A20 A3:A4`,
NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value,
ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value))
f.checked = sync.Map{}
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A7"))
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dvs, 1)
assert.Equal(t, "A3:A6 A8:A10 A15:A20", dvs[0].Sqref)
// Test deleting data validation with invalid sqref
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil,
`A`,
NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value,
ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value))
f.checked = sync.Map{}
assert.Equal(t, f.DeleteDataValidation("Sheet1", "A7"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
})
}
================================================
FILE: date.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"math"
"time"
)
const (
nanosInADay = float64((24 * time.Hour) / time.Nanosecond)
dayNanoseconds = 24 * time.Hour
maxDuration = 290 * 364 * dayNanoseconds
roundEpsilon = 1e-9
)
var (
daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)
excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
)
// timeToExcelTime provides a function to convert time to Excel time.
func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
date := excelMinTime1900
if date1904 {
date = excel1904Epoc
}
if t.Before(date) {
return 0, nil
}
tt, diff, result := t, t.Sub(date), 0.0
for diff >= maxDuration {
result += float64(maxDuration / dayNanoseconds)
tt = tt.Add(-maxDuration)
diff = tt.Sub(date)
}
rem := diff % dayNanoseconds
result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
// Excel dates after 28th February 1900 are actually one day out.
// Excel behaves as though the date 29th February 1900 existed, which it didn't.
// Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
// program that had the majority market share at the time; Lotus 1-2-3.
// https://www.myonlinetraininghub.com/excel-date-and-time
if !date1904 && t.After(excelBuggyPeriodStart) {
result++
}
return result, nil
}
// shiftJulianToNoon provides a function to process julian date to noon.
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
julianDays++
julianFraction -= 0.5
case julianFraction <= -0.5:
julianDays--
julianFraction += 1.5
}
return julianDays, julianFraction
}
// fractionOfADay provides a function to return the integer values for hour,
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
const (
c1us = 1e3
c1s = 1e9
c1day = 24 * 60 * 60 * c1s
)
frac := int64(c1day*fraction + c1us/2)
nanoseconds = int((frac%c1s)/c1us) * c1us
frac /= c1s
seconds = int(frac % 60)
frac /= 60
minutes = int(frac % 60)
hours = int(frac / 60)
return
}
// julianDateToGregorianTime provides a function to convert julian date to
// gregorian time.
func julianDateToGregorianTime(part1, part2 float64) time.Time {
part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2)
julianDays := part1I + part2I
julianFraction := part1F + part2F
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
}
// doTheFliegelAndVanFlandernAlgorithm; By this point generations of
// programmers have repeated the algorithm sent to the editor of
// "Communications of the ACM" in 1968 (published in CACM, volume 11, number
// 10, October 1968, p.657). None of those programmers seems to have found it
// necessary to explain the constants or variable names set out by Henry F.
// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that journal and
// expand an explanation here - that day is not today.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
n := (4 * l) / 146097
l = l - (146097*n+3)/4
i := (4000 * (l + 1)) / 1461001
l = l - (1461*i)/4 + 31
j := (80 * l) / 2447
d := l - (2447*j)/80
l = j / 11
m := j + 2 - (12 * l)
y := 100*(n-49) + i + l
return d, m, y
}
// timeFromExcelTime provides a function to convert an excelTime
// representation (stored as a floating point number) to a time.Time.
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
var date time.Time
wholeDaysPart := int(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
if wholeDaysPart <= 61 {
const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0
const MJD0 float64 = 2400000.5
var date time.Time
if date1904 {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
} else {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
}
return date
}
floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon
if date1904 {
date = excel1904Epoc
} else {
date = excel1900Epoc
}
durationPart := time.Duration(nanosInADay * floatPart)
date = date.AddDate(0, 0, wholeDaysPart).Add(durationPart)
if date.Nanosecond()/1e6 > 500 {
return date.Round(time.Second)
}
return date.Truncate(time.Second)
}
// ExcelDateToTime converts a float-based Excel date representation to a time.Time.
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
if excelDate < 0 {
return time.Time{}, newInvalidExcelDateError(excelDate)
}
return timeFromExcelTime(excelDate, use1904Format), nil
}
// isLeapYear determine if leap year for a given year.
func isLeapYear(y int) bool {
if y == y/400*400 {
return true
}
if y == y/100*100 {
return false
}
return y == y/4*4
}
// getDaysInMonth provides a function to get the days by a given year and
// month number.
func getDaysInMonth(y, m int) int {
if m == 2 && isLeapYear(y) {
return 29
}
return daysInMonth[m-1]
}
// validateDate provides a function to validate if a valid date by a given
// year, month, and day number.
func validateDate(y, m, d int) bool {
if m < 1 || m > 12 {
return false
}
if d < 1 {
return false
}
return d <= getDaysInMonth(y, m)
}
// formatYear converts the given year number into a 4-digit format.
func formatYear(y int) int {
if y < 1900 {
if y < 30 {
y += 2000
} else {
y += 1900
}
}
return y
}
// getDurationNumFmt returns most simplify numbers format code for time
// duration type cell value by given worksheet name, cell reference and number.
func getDurationNumFmt(d time.Duration) int {
if d >= time.Hour*24 {
return 46
}
// Whole minutes
if d.Minutes() == float64(int(d.Minutes())) {
return 20
}
return 21
}
// getTimeNumFmt returns most simplify numbers format code for time type cell
// value by given worksheet name, cell reference and number.
func getTimeNumFmt(t time.Time) int {
nextMonth := t.AddDate(0, 1, 0)
// Whole months
if t.Day() == 1 && nextMonth.Day() == 1 {
return 17
}
// Whole days
if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
return 14
}
return 22
}
================================================
FILE: date_test.go
================================================
package excelize
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type dateTest struct {
ExcelValue float64
GoValue time.Time
}
var trueExpectedDateList = []dateTest{
{0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)},
{25569.000000000000, time.Unix(0, 0).UTC()},
// Expected values extracted from real spreadsheet
{1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
{1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)},
{1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)},
{1.0416666666666667, time.Date(1900, time.January, 1, 1, 0, 0, 0, time.UTC)},
{2.0000000000000000, time.Date(1900, time.January, 2, 0, 0, 0, 0, time.UTC)},
{43269.000000000000, time.Date(2018, time.June, 18, 0, 0, 0, 0, time.UTC)},
{43542.611111111109, time.Date(2019, time.March, 18, 14, 40, 0, 0, time.UTC)},
{401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)},
}
var excelTimeInputList = []dateTest{
{0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
{60.0, time.Date(1900, 2, 28, 0, 0, 0, 0, time.UTC)},
{61.0, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC)},
{41275.0, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)},
{44450.3333333333, time.Date(2021, time.September, 11, 8, 0, 0, 0, time.UTC)},
{401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
}
func TestTimeToExcelTime(t *testing.T) {
for i, test := range trueExpectedDateList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
excelTime, err := timeToExcelTime(test.GoValue, false)
assert.NoError(t, err)
assert.Equalf(t, test.ExcelValue, excelTime,
"Time: %s", test.GoValue.String())
})
}
}
func TestTimeToExcelTime_Timezone(t *testing.T) {
location, err := time.LoadLocation("America/Los_Angeles")
if !assert.NoError(t, err) {
t.FailNow()
}
for i, test := range trueExpectedDateList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
_, err := timeToExcelTime(test.GoValue.In(location), false)
assert.NoError(t, err)
})
}
}
func TestTimeFromExcelTime(t *testing.T) {
for i, test := range excelTimeInputList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
assert.Equal(t, test.GoValue, timeFromExcelTime(test.ExcelValue, false))
})
}
for hour := 0; hour < 24; hour++ {
for minVal := 0; minVal < 60; minVal++ {
for sec := 0; sec < 60; sec++ {
date := time.Date(2021, time.December, 30, hour, minVal, sec, 0, time.UTC)
// Test use 1900 date system
excel1900Time, err := timeToExcelTime(date, false)
assert.NoError(t, err)
date1900Out := timeFromExcelTime(excel1900Time, false)
assert.EqualValues(t, hour, date1900Out.Hour())
assert.EqualValues(t, minVal, date1900Out.Minute())
assert.EqualValues(t, sec, date1900Out.Second())
// Test use 1904 date system
excel1904Time, err := timeToExcelTime(date, true)
assert.NoError(t, err)
date1904Out := timeFromExcelTime(excel1904Time, true)
assert.EqualValues(t, hour, date1904Out.Hour())
assert.EqualValues(t, minVal, date1904Out.Minute())
assert.EqualValues(t, sec, date1904Out.Second())
}
}
}
}
func TestTimeFromExcelTime_1904(t *testing.T) {
julianDays, julianFraction := shiftJulianToNoon(1, -0.6)
assert.Equal(t, julianDays, 0.0)
assert.Equal(t, julianFraction, 0.9)
julianDays, julianFraction = shiftJulianToNoon(1, 0.1)
assert.Equal(t, julianDays, 1.0)
assert.Equal(t, julianFraction, 0.6)
assert.Equal(t, timeFromExcelTime(61, true), time.Date(1904, time.March, 2, 0, 0, 0, 0, time.UTC))
assert.Equal(t, timeFromExcelTime(62, true), time.Date(1904, time.March, 3, 0, 0, 0, 0, time.UTC))
}
func TestExcelDateToTime(t *testing.T) {
// Check normal case
for i, test := range excelTimeInputList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
timeValue, err := ExcelDateToTime(test.ExcelValue, false)
assert.Equal(t, test.GoValue, timeValue)
assert.NoError(t, err)
})
}
// Check error case
_, err := ExcelDateToTime(-1, false)
assert.EqualError(t, err, newInvalidExcelDateError(-1).Error())
}
================================================
FILE: docProps.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
"reflect"
"slices"
"time"
)
// SetAppProps provides a function to set document application properties. The
// properties that can be set are:
//
// Property | Description
// -------------------+--------------------------------------------------------------------------
// Application | The name of the application that created this document.
// |
// ScaleCrop | Indicates the display mode of the document thumbnail. Set this element
// | to 'true' to enable scaling of the document thumbnail to the display. Set
// | this element to 'false' to enable cropping of the document thumbnail to
// | show only sections that will fit the display.
// |
// DocSecurity | Security level of a document as a numeric value. Document security is
// | defined as:
// | 1 - Document is password protected.
// | 2 - Document is recommended to be opened as read-only.
// | 3 - Document is enforced to be opened as read-only.
// | 4 - Document is locked for annotation.
// |
// Company | The name of a company associated with the document.
// |
// LinksUpToDate | Indicates whether hyperlinks in a document are up-to-date. Set this
// | element to 'true' to indicate that hyperlinks are updated. Set this
// | element to 'false' to indicate that hyperlinks are outdated.
// |
// HyperlinksChanged | Specifies that one or more hyperlinks in this part were updated
// | exclusively in this part by a producer. The next producer to open this
// | document shall update the hyperlink relationships with the new
// | hyperlinks specified in this part.
// |
// AppVersion | Specifies the version of the application which produced this document.
// | The content of this element shall be of the form XX.YYYY where X and Y
// | represent numerical values, or the document shall be considered
// | non-conformant.
//
// For example:
//
// err := f.SetAppProps(&excelize.AppProperties{
// Application: "Microsoft Excel",
// ScaleCrop: true,
// DocSecurity: 3,
// Company: "Company Name",
// LinksUpToDate: true,
// HyperlinksChanged: true,
// AppVersion: "16.0000",
// })
func (f *File) SetAppProps(appProperties *AppProperties) error {
app := new(xlsxProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
return err
}
setNoPtrFieldsVal([]string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"},
reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem())
app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value
output, err := xml.Marshal(app)
f.saveFileList(defaultXMLPathDocPropsApp, output)
return err
}
// GetAppProps provides a function to get document application properties.
func (f *File) GetAppProps() (ret *AppProperties, err error) {
app := new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
return
}
ret, err = &AppProperties{
Application: app.Application,
ScaleCrop: app.ScaleCrop,
DocSecurity: app.DocSecurity,
Company: app.Company,
LinksUpToDate: app.LinksUpToDate,
HyperlinksChanged: app.HyperlinksChanged,
AppVersion: app.AppVersion,
}, nil
return
}
// SetDocProps provides a function to set document core properties. The
// properties that can be set are:
//
// Property | Description
// ----------------+-----------------------------------------------------------
// Title | The name given to the resource.
// |
// Subject | The topic of the content of the resource.
// |
// Creator | An entity primarily responsible for making the content of
// | the resource.
// |
// Keywords | A delimited set of keywords to support searching and
// | indexing. This is typically a list of terms that are not
// | available elsewhere in the properties.
// |
// Description | An explanation of the content of the resource.
// |
// LastModifiedBy | The user who performed the last modification. The
// | identification is environment-specific.
// |
// Language | The language of the intellectual content of the resource.
// |
// Identifier | An unambiguous reference to the resource within a given
// | context.
// |
// Revision | The topic of the content of the resource.
// |
// ContentStatus | The status of the content. For example: Values might
// | include "Draft", "Reviewed" and "Final"
// |
// Category | A categorization of the content of this package.
// |
// Version | The version number. This value is set by the user or by
// | the application.
// |
// Created | The created time of the content of the resource which
// | represent in ISO 8601 UTC format, for example
// | "2019-06-04T22:00:10Z".
// |
// Modified | The modified time of the content of the resource which
// | represent in ISO 8601 UTC format, for example
// | "2019-06-04T22:00:10Z".
// |
//
// For example:
//
// err := f.SetDocProps(&excelize.DocProperties{
// Category: "category",
// ContentStatus: "Draft",
// Created: "2019-06-04T22:00:10Z",
// Creator: "Go Excelize",
// Description: "This file created by Go Excelize",
// Identifier: "xlsx",
// Keywords: "Spreadsheet",
// LastModifiedBy: "Go Author",
// Modified: "2019-06-04T22:00:10Z",
// Revision: "0",
// Subject: "Test Subject",
// Title: "Test Title",
// Language: "en-US",
// Version: "1.0.0",
// })
func (f *File) SetDocProps(docProperties *DocProperties) error {
core := new(decodeCoreProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
return err
}
newProps := &xlsxCoreProperties{
Dc: NameSpaceDublinCore,
Dcterms: NameSpaceDublinCoreTerms,
Dcmitype: NameSpaceDublinCoreMetadataInitiative,
XSI: NameSpaceXMLSchemaInstance,
Title: core.Title,
Subject: core.Subject,
Creator: core.Creator,
Keywords: core.Keywords,
Description: core.Description,
LastModifiedBy: core.LastModifiedBy,
Language: core.Language,
Identifier: core.Identifier,
Revision: core.Revision,
ContentStatus: core.ContentStatus,
Category: core.Category,
Version: core.Version,
}
if core.Created != nil {
newProps.Created = &xlsxDcTerms{Type: core.Created.Type, Text: core.Created.Text}
}
if core.Modified != nil {
newProps.Modified = &xlsxDcTerms{Type: core.Modified.Type, Text: core.Modified.Text}
}
setNoPtrFieldsVal([]string{
"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
"LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
}, reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem())
if docProperties.Created != "" {
newProps.Created = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Created}
}
if docProperties.Modified != "" {
newProps.Modified = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Modified}
}
output, err := xml.Marshal(newProps)
f.saveFileList(defaultXMLPathDocPropsCore, output)
return err
}
// GetDocProps provides a function to get document core properties.
func (f *File) GetDocProps() (ret *DocProperties, err error) {
core := new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
return
}
ret, err = &DocProperties{
Category: core.Category,
ContentStatus: core.ContentStatus,
Creator: core.Creator,
Description: core.Description,
Identifier: core.Identifier,
Keywords: core.Keywords,
LastModifiedBy: core.LastModifiedBy,
Revision: core.Revision,
Subject: core.Subject,
Title: core.Title,
Language: core.Language,
Version: core.Version,
}, nil
if core.Created != nil {
ret.Created = core.Created.Text
}
if core.Modified != nil {
ret.Modified = core.Modified.Text
}
return
}
// SetCustomProps provides a function to set custom file properties by given
// property name and value. If the property name already exists, it will be
// updated, otherwise a new property will be added. The value can be of type
// int32, float64, bool, string, time.Time or nil. The property will be delete
// if the value is nil. The function returns an error if the property value is
// not of the correct type.
func (f *File) SetCustomProps(prop CustomProperty) error {
if prop.Name == "" {
return ErrParameterInvalid
}
props := new(decodeCustomProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
Decode(props); err != nil && err != io.EOF {
return err
}
customProps := xlsxCustomProperties{Vt: NameSpaceDocumentPropertiesVariantTypes.Value}
idx, pID := -1, 1
for i := range props.Property {
p := new(xlsxProperty)
setPtrFields(reflect.ValueOf(&props.Property[i]).Elem(), reflect.ValueOf(p).Elem())
if pID < props.Property[i].PID {
pID = props.Property[i].PID
}
if props.Property[i].Name == prop.Name {
idx = i
}
customProps.Property = append(customProps.Property, *p)
}
if idx != -1 && prop.Value == nil {
customProps.Property = slices.Delete(customProps.Property, idx, idx+1)
}
if prop.Value != nil {
property := xlsxProperty{Name: prop.Name, FmtID: EXtURICustomPropertyFmtID}
if err := property.setCustomProps(prop.Value); err != nil {
return err
}
if idx != -1 {
property.PID = customProps.Property[idx].PID
customProps.Property[idx] = property
} else {
property.PID = pID + 1
customProps.Property = append(customProps.Property, property)
}
}
_ = f.addRels(defaultXMLPathRels, SourceRelationshipCustomProperties, defaultXMLPathDocPropsCustom, "")
if err := f.addContentTypePart(0, "customProperties"); err != nil {
return err
}
output, err := xml.Marshal(customProps)
f.saveFileList(defaultXMLPathDocPropsCustom, output)
return err
}
// setCustomProps sets the custom property value based on its type.
func (prop *xlsxProperty) setCustomProps(value interface{}) error {
switch v := value.(type) {
case int32:
prop.I4 = &v
case float64:
prop.R8 = float64Ptr(v)
case bool:
prop.Bool = boolPtr(v)
case string:
prop.Lpwstr = stringPtr(value.(string))
case time.Time:
prop.FileTime = stringPtr(value.(time.Time).Format(time.RFC3339))
default:
return ErrParameterInvalid
}
return nil
}
// GetCustomProps provides a function to get custom file properties.
func (f *File) GetCustomProps() ([]CustomProperty, error) {
var customProps []CustomProperty
props := new(decodeCustomProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
Decode(props); err != nil && err != io.EOF {
return customProps, err
}
for _, p := range props.Property {
prop := CustomProperty{Name: p.Name}
var err error
if prop.Value, err = p.getCustomProps(); err != nil {
return customProps, err
}
customProps = append(customProps, prop)
}
return customProps, nil
}
// getCustomProps gets the custom property value based on its type.
func (p *decodeProperty) getCustomProps() (interface{}, error) {
s := reflect.ValueOf(p).Elem()
for i := range s.NumField() {
if 11 <= i && i <= 20 && !s.Field(i).IsNil() {
return int32(s.Field(i).Elem().Int()), nil // Field vt:i1 to vt:uint
}
if 21 <= i && i <= 22 && !s.Field(i).IsNil() {
return s.Field(i).Elem().Float(), nil // Field vt:r4 to vt:r8
}
if p.Bool != nil {
return *p.Bool, nil
}
if p.Lpwstr != nil {
return *p.Lpwstr, nil
}
if p.FileTime != nil {
return time.Parse(time.RFC3339, *p.FileTime)
}
}
return nil, nil
}
================================================
FILE: docProps_test.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"fmt"
"path/filepath"
"slices"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var MacintoshCyrillicCharset = []byte{0x8F, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x20, 0xEC, 0xE8, 0xF0}
func TestSetAppProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SetAppProps(&AppProperties{
Application: "Microsoft Excel",
ScaleCrop: true,
DocSecurity: 3,
Company: "Company Name",
LinksUpToDate: true,
HyperlinksChanged: true,
AppVersion: "16.0000",
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetAppProps.xlsx")))
f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
assert.NoError(t, f.SetAppProps(&AppProperties{}))
assert.NoError(t, f.Close())
// Test unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetAppProps(&AppProperties{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetAppProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
props, err := f.GetAppProps()
assert.NoError(t, err)
assert.Equal(t, props.Application, "Microsoft Macintosh Excel")
f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
_, err = f.GetAppProps()
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test get application properties with unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
_, err = f.GetAppProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDocProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SetDocProps(&DocProperties{
Category: "category",
ContentStatus: "Draft",
Created: "2019-06-04T22:00:10Z",
Creator: "Go Excelize",
Description: "This file created by Go Excelize",
Identifier: "xlsx",
Keywords: "Spreadsheet",
LastModifiedBy: "Go Author",
Modified: "2019-06-04T22:00:10Z",
Revision: "0",
Subject: "Test Subject",
Title: "Test Title",
Language: "en-US",
Version: "1.0.0",
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
assert.NoError(t, f.SetDocProps(&DocProperties{}))
assert.NoError(t, f.Close())
// Test unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetDocProps(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
props, err := f.GetDocProps()
assert.NoError(t, err)
assert.Equal(t, props.Creator, "Microsoft Office User")
f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
_, err = f.GetDocProps()
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test get workbook properties with unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
_, err = f.GetDocProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestCustomProps(t *testing.T) {
f := NewFile()
expected := []CustomProperty{
{Name: "Text Prop", Value: "text"},
{Name: "Boolean Prop 1", Value: true},
{Name: "Boolean Prop 2", Value: false},
{Name: "Number Prop 1", Value: -123.456},
{Name: "Number Prop 2", Value: int32(1)},
{Name: "Date Prop", Value: time.Date(2021, time.September, 11, 0, 0, 0, 0, time.UTC)},
}
for _, prop := range expected {
assert.NoError(t, f.SetCustomProps(prop))
}
props, err := f.GetCustomProps()
assert.NoError(t, err)
assert.Equal(t, expected, props)
// Test delete custom property
assert.NoError(t, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 1", Value: nil}))
props, err = f.GetCustomProps()
assert.NoError(t, err)
expected = slices.Delete(expected, 1, 2)
assert.Equal(t, expected, props)
// Test change custom property value data type
assert.NoError(t, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 2", Value: "true"}))
props, err = f.GetCustomProps()
assert.NoError(t, err)
assert.Equal(t, props[1].Value, "true")
// Test set custom property with unsupported value data type
assert.Equal(t, ErrParameterInvalid, f.SetCustomProps(CustomProperty{Name: "Boolean Prop 2", Value: 1}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCustomProps.xlsx")))
assert.NoError(t, f.Close())
// Test set custom property without property name
f = NewFile()
assert.Equal(t, ErrParameterInvalid, f.SetCustomProps(CustomProperty{}))
// Test set custom property with unsupported charset
f.Pkg.Store(defaultXMLPathDocPropsCustom, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCustomProps(CustomProperty{Name: "Prop"}), "XML syntax error on line 1: invalid UTF-8")
// Test get custom property with unsupported charset
_, err = f.GetCustomProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test set custom property with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCustomProps(CustomProperty{Name: "Prop"}), "XML syntax error on line 1: invalid UTF-8")
// Test get custom property with unsupported charset
f.Pkg.Store(defaultXMLPathDocPropsCustom, []byte(fmt.Sprintf(`x`, NameSpaceDocumentPropertiesVariantTypes, EXtURICustomPropertyFmtID)))
_, err = f.GetCustomProps()
assert.EqualError(t, err, "parsing time \"x\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"x\" as \"2006\"")
// Test get custom property with unsupported value data type
f.Pkg.Store(defaultXMLPathDocPropsCustom, []byte(fmt.Sprintf(``, NameSpaceDocumentPropertiesVariantTypes, EXtURICustomPropertyFmtID)))
props, err = f.GetCustomProps()
assert.Equal(t, []CustomProperty{{Name: "Prop"}}, props)
assert.NoError(t, err)
assert.NoError(t, f.Close())
}
================================================
FILE: drawing.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
"reflect"
"strconv"
"strings"
)
// prepareDrawing provides a function to prepare drawing ID and XML by given
// drawingID, worksheet name and default drawingXML.
func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
if ws.Drawing != nil {
// The worksheet already has a picture or chart relationships, use the
// relationships drawing ../drawings/drawing%d.xml or /xl/drawings/drawing%d.xml.
sheetRelationshipsDrawingXML = strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "/xl/drawings/", "../drawings/")
drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
} else {
// Add first picture for given sheet.
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetDrawing(sheet, rID)
}
return drawingID, drawingXML
}
// prepareChartSheetDrawing provides a function to prepare drawing ID and XML
// by given drawingID, worksheet name and default drawingXML.
func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) {
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
// Only allow one chart in a chartsheet.
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/chartsheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetNameSpace(sheet, SourceRelationship)
cs.Drawing = &xlsxDrawing{
RID: "rId" + strconv.Itoa(rID),
}
}
// addChart provides a function to create chart as xl/charts/chart%d.xml by
// given format sets.
func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
count := f.countCharts()
xlsxChartSpace := xlsxChartSpace{
XMLNSa: NameSpaceDrawingML.Value,
Date1904: &attrValBool{Val: boolPtr(false)},
Lang: &attrValString{Val: stringPtr("en-US")},
RoundedCorners: &attrValBool{Val: boolPtr(false)},
Chart: cChart{
Title: f.drawPlotAreaTitles(opts.Title, ""),
View3D: &cView3D{
RotX: &attrValInt{Val: intPtr(chartView3DRotX[opts.Type])},
RotY: &attrValInt{Val: intPtr(chartView3DRotY[opts.Type])},
Perspective: &attrValInt{Val: intPtr(chartView3DPerspective[opts.Type])},
RAngAx: &attrValInt{Val: intPtr(chartView3DRAngAx[opts.Type])},
},
Floor: &cThicknessSpPr{
Thickness: &attrValInt{Val: intPtr(0)},
},
SideWall: &cThicknessSpPr{
Thickness: &attrValInt{Val: intPtr(0)},
},
BackWall: &cThicknessSpPr{
Thickness: &attrValInt{Val: intPtr(0)},
},
PlotArea: &cPlotArea{},
PlotVisOnly: &attrValBool{Val: boolPtr(false)},
DispBlanksAs: &attrValString{Val: stringPtr(opts.ShowBlanksAs)},
ShowDLblsOverMax: &attrValBool{Val: boolPtr(false)},
},
SpPr: &cSpPr{
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{Val: "bg1"},
},
Ln: f.drawChartLn(&opts.Border),
},
PrintSettings: &cPrintSettings{
PageMargins: &cPageMargins{
B: 0.75,
L: 0.7,
R: 0.7,
T: 0.7,
Header: 0.3,
Footer: 0.3,
},
},
}
xlsxChartSpace.SpPr = f.drawShapeFill(opts.Fill, xlsxChartSpace.SpPr)
plotAreaFunc := map[ChartType]func(pa *cPlotArea, opts *Chart) *cPlotArea{
Area: f.drawBaseChart,
AreaStacked: f.drawBaseChart,
AreaPercentStacked: f.drawBaseChart,
Area3D: f.drawBaseChart,
Area3DStacked: f.drawBaseChart,
Area3DPercentStacked: f.drawBaseChart,
Bar: f.drawBaseChart,
BarStacked: f.drawBaseChart,
BarPercentStacked: f.drawBaseChart,
Bar3DClustered: f.drawBaseChart,
Bar3DStacked: f.drawBaseChart,
Bar3DPercentStacked: f.drawBaseChart,
Bar3DConeClustered: f.drawBaseChart,
Bar3DConeStacked: f.drawBaseChart,
Bar3DConePercentStacked: f.drawBaseChart,
Bar3DPyramidClustered: f.drawBaseChart,
Bar3DPyramidStacked: f.drawBaseChart,
Bar3DPyramidPercentStacked: f.drawBaseChart,
Bar3DCylinderClustered: f.drawBaseChart,
Bar3DCylinderStacked: f.drawBaseChart,
Bar3DCylinderPercentStacked: f.drawBaseChart,
Col: f.drawBaseChart,
ColStacked: f.drawBaseChart,
ColPercentStacked: f.drawBaseChart,
Col3D: f.drawBaseChart,
Col3DClustered: f.drawBaseChart,
Col3DStacked: f.drawBaseChart,
Col3DPercentStacked: f.drawBaseChart,
Col3DCone: f.drawBaseChart,
Col3DConeClustered: f.drawBaseChart,
Col3DConeStacked: f.drawBaseChart,
Col3DConePercentStacked: f.drawBaseChart,
Col3DPyramid: f.drawBaseChart,
Col3DPyramidClustered: f.drawBaseChart,
Col3DPyramidStacked: f.drawBaseChart,
Col3DPyramidPercentStacked: f.drawBaseChart,
Col3DCylinder: f.drawBaseChart,
Col3DCylinderClustered: f.drawBaseChart,
Col3DCylinderStacked: f.drawBaseChart,
Col3DCylinderPercentStacked: f.drawBaseChart,
Doughnut: f.drawDoughnutChart,
Line: f.drawLineChart,
Line3D: f.drawLine3DChart,
Pie: f.drawPieChart,
Pie3D: f.drawPie3DChart,
PieOfPie: f.drawPieOfPieChart,
BarOfPie: f.drawBarOfPieChart,
Radar: f.drawRadarChart,
Scatter: f.drawScatterChart,
Surface3D: f.drawSurface3DChart,
WireframeSurface3D: f.drawSurface3DChart,
Contour: f.drawSurfaceChart,
WireframeContour: f.drawSurfaceChart,
Bubble: f.drawBubbleChart,
Bubble3D: f.drawBubbleChart,
StockHighLowClose: f.drawStockChart,
StockOpenHighLowClose: f.drawStockChart,
}
xlsxChartSpace.Chart.drawChartLegend(opts)
xlsxChartSpace.Chart.PlotArea.SpPr = f.drawShapeFill(opts.PlotArea.Fill, xlsxChartSpace.Chart.PlotArea.SpPr)
xlsxChartSpace.Chart.PlotArea.DTable = f.drawPlotAreaDTable(opts)
addChart := func(c, p *cPlotArea) {
immutable, mutable := reflect.ValueOf(c).Elem(), reflect.ValueOf(p).Elem()
for i := 0; i < mutable.NumField(); i++ {
field := mutable.Field(i)
if field.IsNil() {
continue
}
fld := immutable.FieldByName(mutable.Type().Field(i).Name)
if field.Kind() == reflect.Slice && i < 17 { // All []*cCharts type fields
fld.Set(reflect.Append(fld, field.Index(0)))
continue
}
fld.Set(field)
}
}
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](xlsxChartSpace.Chart.PlotArea, opts))
order := len(opts.Series)
for idx := range comboCharts {
comboCharts[idx].order = order
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](xlsxChartSpace.Chart.PlotArea, comboCharts[idx]))
order += len(comboCharts[idx].Series)
}
// If the dateAx field exists, valAx field should be nil.
if xlsxChartSpace.Chart.PlotArea != nil && xlsxChartSpace.Chart.PlotArea.DateAx != nil {
xlsxChartSpace.Chart.PlotArea.CatAx = nil
}
chart, _ := xml.Marshal(xlsxChartSpace)
media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml"
f.saveFileList(media, chart)
}
// drawBaseChart provides a function to draw the c:plotArea element for bar,
// and column series charts by given format sets.
func (f *File) drawBaseChart(pa *cPlotArea, opts *Chart) *cPlotArea {
c := []*cCharts{
{
BarDir: &attrValString{
Val: stringPtr("col"),
},
Grouping: &attrValString{
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
},
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
Shape: f.drawChartShape(opts),
DLbls: f.drawChartDLbls(opts),
GapWidth: f.drawChartGapWidth(opts),
AxID: f.genAxID(opts),
Overlap: f.drawChartOverlap(opts),
},
}
var ok bool
if *c[0].BarDir.Val, ok = plotAreaChartBarDir[opts.Type]; !ok {
c[0].BarDir = nil
if opts.XAxis.DropLines {
c[0].DropLines = &cChartLines{}
}
}
catAx := f.drawPlotAreaCatAx(pa, opts)
valAx := f.drawPlotAreaValAx(pa, opts)
charts := map[ChartType]*cPlotArea{
Area: {
AreaChart: c,
CatAx: catAx,
ValAx: valAx,
},
AreaStacked: {
AreaChart: c,
CatAx: catAx,
ValAx: valAx,
},
AreaPercentStacked: {
AreaChart: c,
CatAx: catAx,
ValAx: valAx,
},
Area3D: {
Area3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Area3DStacked: {
Area3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Area3DPercentStacked: {
Area3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
BarStacked: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
BarPercentStacked: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DConeClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DConeStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DConePercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DPyramidClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DPyramidStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DPyramidPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DCylinderClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DCylinderStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bar3DCylinderPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
ColStacked: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
ColPercentStacked: {
BarChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3D: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DCone: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DConeClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DConeStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DConePercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DPyramid: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DPyramidClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DPyramidStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DPyramidPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DCylinder: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DCylinderClustered: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DCylinderStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Col3DCylinderPercentStacked: {
Bar3DChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bubble: {
BubbleChart: c,
CatAx: catAx,
ValAx: valAx,
},
Bubble3D: {
BubbleChart: c,
CatAx: catAx,
ValAx: valAx,
},
}
return charts[opts.Type]
}
// drawDoughnutChart provides a function to draw the c:plotArea element for
// doughnut chart by given format sets.
func (f *File) drawDoughnutChart(pa *cPlotArea, opts *Chart) *cPlotArea {
holeSize := 75
if opts.HoleSize > 0 && opts.HoleSize <= 90 {
holeSize = opts.HoleSize
}
return &cPlotArea{
DoughnutChart: []*cCharts{
{
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
HoleSize: &attrValInt{Val: intPtr(holeSize)},
},
},
}
}
// drawLineChart provides a function to draw the c:plotArea element for line
// chart by given format sets.
func (f *File) drawLineChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
LineChart: []*cCharts{
{
Grouping: &attrValString{
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
},
VaryColors: &attrValBool{
Val: boolPtr(false),
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
CatAx: f.drawPlotAreaCatAx(pa, opts),
ValAx: f.drawPlotAreaValAx(pa, opts),
}
if opts.XAxis.DropLines {
plotArea.LineChart[0].DropLines = &cChartLines{}
}
if opts.XAxis.HighLowLines {
plotArea.LineChart[0].HiLowLines = &cChartLines{}
}
return plotArea
}
// drawLine3DChart provides a function to draw the c:plotArea element for line
// chart by given format sets.
func (f *File) drawLine3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
Line3DChart: []*cCharts{
{
Grouping: &attrValString{
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
},
VaryColors: &attrValBool{
Val: boolPtr(false),
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
CatAx: f.drawPlotAreaCatAx(pa, opts),
ValAx: f.drawPlotAreaValAx(pa, opts),
}
if opts.XAxis.DropLines {
plotArea.Line3DChart[0].DropLines = &cChartLines{}
}
return plotArea
}
// drawPieChart provides a function to draw the c:plotArea element for pie
// chart by given format sets.
func (f *File) drawPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
return &cPlotArea{
PieChart: []*cCharts{
{
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
},
},
}
}
// drawPie3DChart provides a function to draw the c:plotArea element for 3D
// pie chart by given format sets.
func (f *File) drawPie3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
return &cPlotArea{
Pie3DChart: []*cCharts{
{
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
},
},
}
}
// drawPieOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets.
func (f *File) drawPieOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
var splitPos *attrValInt
if opts.PlotArea.SecondPlotValues > 0 {
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
}
return &cPlotArea{
OfPieChart: []*cCharts{
{
OfPieType: &attrValString{
Val: stringPtr("pie"),
},
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
SplitPos: splitPos,
SerLines: &attrValString{},
},
},
}
}
// drawBarOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets.
func (f *File) drawBarOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
var splitPos *attrValInt
if opts.PlotArea.SecondPlotValues > 0 {
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
}
return &cPlotArea{
OfPieChart: []*cCharts{
{
OfPieType: &attrValString{
Val: stringPtr("bar"),
},
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
SplitPos: splitPos,
Ser: f.drawChartSeries(opts),
SerLines: &attrValString{},
},
},
}
}
// drawRadarChart provides a function to draw the c:plotArea element for radar
// chart by given format sets.
func (f *File) drawRadarChart(pa *cPlotArea, opts *Chart) *cPlotArea {
return &cPlotArea{
RadarChart: []*cCharts{
{
RadarStyle: &attrValString{
Val: stringPtr("marker"),
},
VaryColors: &attrValBool{
Val: boolPtr(false),
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
CatAx: f.drawPlotAreaCatAx(pa, opts),
ValAx: f.drawPlotAreaValAx(pa, opts),
}
}
// drawScatterChart provides a function to draw the c:plotArea element for
// scatter chart by given format sets.
func (f *File) drawScatterChart(pa *cPlotArea, opts *Chart) *cPlotArea {
return &cPlotArea{
ScatterChart: []*cCharts{
{
ScatterStyle: &attrValString{
Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker
},
VaryColors: &attrValBool{
Val: boolPtr(false),
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...),
}
}
// drawSurface3DChart provides a function to draw the c:surface3DChart element by
// given format sets.
func (f *File) drawSurface3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
Surface3DChart: []*cCharts{
{
Ser: f.drawChartSeries(opts),
AxID: []*attrValInt{
{Val: intPtr(100000000)},
{Val: intPtr(100000001)},
{Val: intPtr(100000005)},
},
},
},
CatAx: f.drawPlotAreaCatAx(pa, opts),
ValAx: f.drawPlotAreaValAx(pa, opts),
SerAx: f.drawPlotAreaSerAx(opts),
}
if opts.Type == WireframeSurface3D {
plotArea.Surface3DChart[0].Wireframe = &attrValBool{Val: boolPtr(true)}
}
return plotArea
}
// drawSurfaceChart provides a function to draw the c:surfaceChart element by
// given format sets.
func (f *File) drawSurfaceChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
SurfaceChart: []*cCharts{
{
Ser: f.drawChartSeries(opts),
AxID: []*attrValInt{
{Val: intPtr(100000000)},
{Val: intPtr(100000001)},
{Val: intPtr(100000005)},
},
},
},
CatAx: f.drawPlotAreaCatAx(pa, opts),
ValAx: f.drawPlotAreaValAx(pa, opts),
SerAx: f.drawPlotAreaSerAx(opts),
}
if opts.Type == WireframeContour {
plotArea.SurfaceChart[0].Wireframe = &attrValBool{Val: boolPtr(true)}
}
return plotArea
}
// drawBubbleChart provides a function to draw the c:bubbleChart element by
// given format sets.
func (f *File) drawBubbleChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
BubbleChart: []*cCharts{
{
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...),
}
if opts.BubbleSize > 0 && opts.BubbleSize <= 300 {
plotArea.BubbleChart[0].BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))}
}
return plotArea
}
// drawStockChart provides a function to draw the c:stockChart element by
// given format sets.
func (f *File) drawStockChart(pa *cPlotArea, opts *Chart) *cPlotArea {
plotArea := &cPlotArea{
StockChart: []*cCharts{
{
VaryColors: &attrValBool{
Val: opts.VaryColors,
},
Ser: f.drawChartSeries(opts),
DLbls: f.drawChartDLbls(opts),
AxID: f.genAxID(opts),
},
},
ValAx: f.drawPlotAreaValAx(pa, opts),
DateAx: f.drawPlotAreaCatAx(pa, opts),
}
if opts.Type == StockHighLowClose {
plotArea.StockChart[0].HiLowLines = &cChartLines{}
}
if opts.Type == StockOpenHighLowClose {
plotArea.StockChart[0].HiLowLines = &cChartLines{}
plotArea.StockChart[0].UpDownBars = &cUpDownBars{
GapWidth: &attrValString{Val: stringPtr("150")},
UpBars: &cChartLines{f.drawShapeFill(opts.PlotArea.UpBars.Fill, &cSpPr{Ln: f.drawChartLn(&opts.PlotArea.UpBars.Border)})},
DownBars: &cChartLines{f.drawShapeFill(opts.PlotArea.DownBars.Fill, &cSpPr{Ln: f.drawChartLn(&opts.PlotArea.UpBars.Border)})},
}
}
ser := *plotArea.StockChart[0].Ser
ser[0].Val.NumRef.NumCache = &cNumCache{}
return plotArea
}
// drawChartGapWidth provides a function to draw the c:gapWidth element by given
// format sets.
func (f *File) drawChartGapWidth(opts *Chart) *attrValInt {
for _, t := range barColChartTypes {
if t == opts.Type && opts.GapWidth != nil && *opts.GapWidth != 150 && *opts.GapWidth <= 500 {
return &attrValInt{intPtr(int(*opts.GapWidth))}
}
}
return nil
}
// drawChartOverlap provides a function to draw the c:overlap element by given
// format sets.
func (f *File) drawChartOverlap(opts *Chart) *attrValInt {
var val *attrValInt
if _, ok := plotAreaChartOverlap[opts.Type]; ok {
val = &attrValInt{intPtr(100)}
}
if opts.Overlap != nil && -100 <= *opts.Overlap && *opts.Overlap <= 100 {
val = &attrValInt{intPtr(*opts.Overlap)}
}
for _, t := range barColChartTypes {
if t == opts.Type {
return val
}
}
return nil
}
// drawChartShape provides a function to draw the c:shape element by given
// format sets.
func (f *File) drawChartShape(opts *Chart) *attrValString {
shapes := map[ChartType]string{
Bar3DConeClustered: "cone",
Bar3DConeStacked: "cone",
Bar3DConePercentStacked: "cone",
Bar3DPyramidClustered: "pyramid",
Bar3DPyramidStacked: "pyramid",
Bar3DPyramidPercentStacked: "pyramid",
Bar3DCylinderClustered: "cylinder",
Bar3DCylinderStacked: "cylinder",
Bar3DCylinderPercentStacked: "cylinder",
Col3DCone: "cone",
Col3DConeClustered: "cone",
Col3DConeStacked: "cone",
Col3DConePercentStacked: "cone",
Col3DPyramid: "pyramid",
Col3DPyramidClustered: "pyramid",
Col3DPyramidStacked: "pyramid",
Col3DPyramidPercentStacked: "pyramid",
Col3DCylinder: "cylinder",
Col3DCylinderClustered: "cylinder",
Col3DCylinderStacked: "cylinder",
Col3DCylinderPercentStacked: "cylinder",
}
if shape, ok := shapes[opts.Type]; ok {
return &attrValString{Val: stringPtr(shape)}
}
return nil
}
// drawChartSeries provides a function to draw the c:ser element by given
// format sets.
func (f *File) drawChartSeries(opts *Chart) *[]cSer {
var ser []cSer
for k := range opts.Series {
ser = append(ser, cSer{
IDx: &attrValInt{Val: intPtr(k + opts.order)},
Order: &attrValInt{Val: intPtr(k + opts.order)},
Tx: &cTx{
StrRef: &cStrRef{
F: opts.Series[k].Name,
},
},
SpPr: f.drawChartSeriesSpPr(k, opts),
Marker: f.drawChartSeriesMarker(k, opts),
DPt: f.drawChartSeriesDPt(k, opts),
DLbls: f.drawChartSeriesDLbls(k, opts),
InvertIfNegative: &attrValBool{Val: boolPtr(false)},
Cat: f.drawChartSeriesCat(opts.Series[k], opts),
Smooth: &attrValBool{Val: boolPtr(opts.Series[k].Line.Smooth)},
Val: f.drawChartSeriesVal(opts.Series[k], opts),
XVal: f.drawChartSeriesXVal(opts.Series[k], opts),
YVal: f.drawChartSeriesYVal(opts.Series[k], opts),
BubbleSize: f.drawCharSeriesBubbleSize(opts.Series[k], opts),
Bubble3D: f.drawCharSeriesBubble3D(opts),
})
}
return &ser
}
// drawShapeFill provides a function to draw the a:solidFill element by given
// fill format sets.
func (f *File) drawShapeFill(fill Fill, spPr *cSpPr) *cSpPr {
if fill.Type == "pattern" && fill.Pattern == 1 {
if spPr == nil {
spPr = &cSpPr{}
}
if len(fill.Color) == 1 {
spPr.SolidFill = &aSolidFill{SrgbClr: &aSrgbClr{Val: stringPtr(strings.TrimPrefix(fill.Color[0], "#"))}}
if fill.Transparency > 0 {
val := (100 - fill.Transparency) * 1000
spPr.SolidFill.SrgbClr.Alpha = &attrValInt{Val: &val}
}
return spPr
}
spPr.SolidFill = nil
spPr.NoFill = stringPtr("")
}
return spPr
}
// drawChartSeriesSpPr provides a function to draw the c:spPr element by given
// format sets.
func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr {
spPr := &cSpPr{SolidFill: &aSolidFill{SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa((opts.order+i)%6+1)}}}
spPr = f.drawShapeFill(opts.Series[i].Fill, spPr)
solid := &cSpPr{
Ln: &aLn{
W: f.ptToEMUs(opts.Series[i].Line.Width),
Cap: "rnd", // rnd, sq, flat
SolidFill: spPr.SolidFill,
},
}
if opts.Series[i].Line.Dash != ChartDashUnset {
solid.Ln.PrstDash = &attrValString{Val: stringPtr(chartDashTypes[opts.Series[i].Line.Dash])}
}
noLn := &cSpPr{Ln: &aLn{NoFill: &attrValString{}}}
if chartSeriesSpPr, ok := map[ChartType]map[ChartLineType]*cSpPr{
Line: {ChartLineUnset: solid, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: solid},
Scatter: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn},
StockHighLowClose: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn},
StockOpenHighLowClose: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn},
}[opts.Type]; ok {
return chartSeriesSpPr[opts.Series[i].Line.Type]
}
if spPr.SolidFill.SrgbClr != nil {
return spPr
}
return nil
}
// drawChartSeriesDPt provides a function to draw the c:dPt element by given
// data index and format sets.
func (f *File) drawChartSeriesDPt(i int, opts *Chart) []*cDPt {
dpt := []*cDPt{{
IDx: &attrValInt{Val: intPtr(i)},
Bubble3D: &attrValBool{Val: boolPtr(false)},
SpPr: &cSpPr{
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)},
},
Ln: &aLn{
W: 25400,
Cap: "rnd",
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{Val: "lt1"},
},
},
Sp3D: &aSp3D{
ContourW: 25400,
ContourClr: &aContourClr{
SchemeClr: &aSchemeClr{Val: "lt1"},
},
},
},
}}
if len(opts.Series[i].DataPoint) > 0 {
dpt = []*cDPt{}
}
for j := 0; j < len(opts.Series[i].DataPoint); j++ {
spPr := &cSpPr{}
dpt = append(dpt, &cDPt{
IDx: &attrValInt{Val: intPtr(opts.Series[i].DataPoint[j].Index)},
SpPr: f.drawShapeFill(opts.Series[i].DataPoint[j].Fill, spPr),
})
}
chartSeriesDPt := map[ChartType][]*cDPt{Doughnut: dpt, Pie: dpt, Pie3D: dpt}
return chartSeriesDPt[opts.Type]
}
// drawChartSeriesCat provides a function to draw the c:cat element by given
// chart series and format sets.
func (f *File) drawChartSeriesCat(v ChartSeries, opts *Chart) *cCat {
cat := &cCat{
StrRef: &cStrRef{
F: v.Categories,
},
}
chartSeriesCat := map[ChartType]*cCat{Scatter: nil, Bubble: nil, Bubble3D: nil}
if _, ok := chartSeriesCat[opts.Type]; ok || v.Categories == "" {
return nil
}
return cat
}
// drawChartSeriesVal provides a function to draw the c:val element by given
// chart series and format sets.
func (f *File) drawChartSeriesVal(v ChartSeries, opts *Chart) *cVal {
val := &cVal{
NumRef: &cNumRef{
F: v.Values,
NumCache: &cNumCache{},
},
}
chartSeriesVal := map[ChartType]*cVal{Scatter: nil, Bubble: nil, Bubble3D: nil}
if _, ok := chartSeriesVal[opts.Type]; ok {
return nil
}
return val
}
// drawChartSeriesMarker provides a function to draw the c:marker element by
// given data index and format sets.
func (f *File) drawChartSeriesMarker(i int, opts *Chart) *cMarker {
defaultSymbol := map[ChartType]*attrValString{
Scatter: {Val: stringPtr("circle")},
StockHighLowClose: {Val: stringPtr("dot")},
StockOpenHighLowClose: {Val: stringPtr("none")},
}
marker := &cMarker{
Symbol: defaultSymbol[opts.Type],
Size: &attrValInt{Val: intPtr(5)},
}
if symbol := stringPtr(opts.Series[i].Marker.Symbol); *symbol != "" {
marker.Symbol = &attrValString{Val: symbol}
}
if size := intPtr(opts.Series[i].Marker.Size); *size != 0 {
marker.Size = &attrValInt{Val: size}
}
if i < 6 {
marker.SpPr = &cSpPr{SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)},
}, Ln: &aLn{W: 9252}}
}
marker.SpPr = f.drawShapeFill(opts.Series[i].Marker.Fill, marker.SpPr)
if marker.SpPr != nil && marker.SpPr.Ln != nil {
marker.SpPr.Ln = f.drawChartLn(&opts.Series[i].Marker.Border)
}
chartSeriesMarker := map[ChartType]*cMarker{
Scatter: marker,
Line: marker,
StockHighLowClose: marker,
StockOpenHighLowClose: marker,
}
return chartSeriesMarker[opts.Type]
}
// drawChartSeriesXVal provides a function to draw the c:xVal element by given
// chart series and format sets.
func (f *File) drawChartSeriesXVal(v ChartSeries, opts *Chart) *cCat {
cat := &cCat{
StrRef: &cStrRef{
F: v.Categories,
},
}
chartSeriesXVal := map[ChartType]*cCat{Scatter: cat, Bubble: cat, Bubble3D: cat}
return chartSeriesXVal[opts.Type]
}
// drawChartSeriesYVal provides a function to draw the c:yVal element by given
// chart series and format sets.
func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal {
val := &cVal{
NumRef: &cNumRef{
F: v.Values,
NumCache: &cNumCache{},
},
}
chartSeriesYVal := map[ChartType]*cVal{Scatter: val, Bubble: val, Bubble3D: val}
return chartSeriesYVal[opts.Type]
}
// drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize
// element by given chart series and format sets.
func (f *File) drawCharSeriesBubbleSize(v ChartSeries, opts *Chart) *cVal {
if _, ok := map[ChartType]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok {
return nil
}
fVal := v.Values
if v.Sizes != "" {
fVal = v.Sizes
}
return &cVal{
NumRef: &cNumRef{
F: fVal,
NumCache: &cNumCache{},
},
}
}
// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element
// by given format sets.
func (f *File) drawCharSeriesBubble3D(opts *Chart) *attrValBool {
if _, ok := map[ChartType]bool{Bubble3D: true}[opts.Type]; !ok {
return nil
}
return &attrValBool{Val: boolPtr(true)}
}
// drawChartNumFmt provides a function to draw the c:numFmt element by given
// data labels format sets.
func (f *File) drawChartNumFmt(labels ChartNumFmt) *cNumFmt {
var numFmt *cNumFmt
if labels.CustomNumFmt != "" || labels.SourceLinked {
numFmt = &cNumFmt{
FormatCode: labels.CustomNumFmt,
SourceLinked: labels.SourceLinked,
}
}
return numFmt
}
// drawChartDLbls provides a function to draw the c:dLbls element by given
// format sets.
func (f *File) drawChartDLbls(opts *Chart) *cDLbls {
return &cDLbls{
NumFmt: f.drawChartNumFmt(opts.PlotArea.NumFmt),
ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)},
ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)},
ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)},
ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)},
ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)},
ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)},
ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)},
}
}
// inSupportedChartDataLabelsPositionType provides a method to check if an
// element is present in an array, and return the index of its location,
// otherwise return -1.
func inSupportedChartDataLabelsPositionType(a []ChartDataLabelPositionType, x ChartDataLabelPositionType) int {
for idx, n := range a {
if x == n {
return idx
}
}
return -1
}
// drawChartSeriesDLbls provides a function to draw the c:dLbls element by
// given format sets.
func (f *File) drawChartSeriesDLbls(i int, opts *Chart) *cDLbls {
dLbls := f.drawChartDLbls(opts)
chartSeriesDLbls := map[ChartType]*cDLbls{
Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil,
}
if _, ok := chartSeriesDLbls[opts.Type]; ok {
return nil
}
if types, ok := supportedChartDataLabelsPosition[opts.Type]; ok && opts.Series[i].DataLabelPosition != ChartDataLabelsPositionUnset {
if inSupportedChartDataLabelsPositionType(types, opts.Series[i].DataLabelPosition) != -1 {
dLbls.DLblPos = &attrValString{Val: stringPtr(chartDataLabelsPositionTypes[opts.Series[i].DataLabelPosition])}
}
}
dLbl := opts.Series[i].DataLabel
dLbls.SpPr = f.drawShapeFill(dLbl.Fill, dLbls.SpPr)
dLbls.TxPr = &cTxPr{BodyPr: aBodyPr{}, P: aP{PPr: &aPPr{DefRPr: aRPr{}}}}
drawChartFont(&dLbl.Font, &dLbls.TxPr.P.PPr.DefRPr)
return dLbls
}
// drawPlotAreaCatAx provides a function to draw the c:catAx element.
func (f *File) drawPlotAreaCatAx(pa *cPlotArea, opts *Chart) []*cAxs {
maxVal := &attrValFloat{Val: opts.XAxis.Maximum}
minVal := &attrValFloat{Val: opts.XAxis.Minimum}
if opts.XAxis.Maximum == nil {
maxVal = nil
}
if opts.XAxis.Minimum == nil {
minVal = nil
}
ax := &cAxs{
AxID: &attrValInt{Val: intPtr(100000000)},
Scaling: &cScaling{
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
Max: maxVal,
Min: minVal,
},
Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)},
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
NumFmt: &cNumFmt{FormatCode: "General"},
MajorTickMark: &attrValString{Val: stringPtr("none")},
MinorTickMark: &attrValString{Val: stringPtr("none")},
Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""),
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.XAxis.TickLabelPosition])},
SpPr: f.drawPlotAreaSpPr(),
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
CrossAx: &attrValInt{Val: intPtr(100000001)},
Crosses: &attrValString{Val: stringPtr("autoZero")},
Auto: &attrValBool{Val: boolPtr(true)},
LblAlgn: &attrValString{Val: stringPtr("ctr")},
LblOffset: &attrValInt{Val: intPtr(100)},
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
}
if numFmt := f.drawChartNumFmt(opts.XAxis.NumFmt); numFmt != nil {
ax.NumFmt = numFmt
}
if opts.XAxis.MajorGridLines {
ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.XAxis.MinorGridLines {
ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.XAxis.TickLabelSkip != 0 {
ax.TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)}
}
if opts.order > 0 && opts.YAxis.Secondary && pa.CatAx != nil {
ax.AxID = &attrValInt{Val: intPtr(opts.XAxis.axID)}
ax.Delete = &attrValBool{Val: boolPtr(true)}
ax.Crosses = nil
ax.CrossAx = &attrValInt{Val: intPtr(opts.YAxis.axID)}
return []*cAxs{pa.CatAx[0], ax}
}
return []*cAxs{ax}
}
// drawPlotAreaValAx provides a function to draw the c:valAx element.
func (f *File) drawPlotAreaValAx(pa *cPlotArea, opts *Chart) []*cAxs {
maxVal := &attrValFloat{Val: opts.YAxis.Maximum}
minVal := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil {
maxVal = nil
}
if opts.YAxis.Minimum == nil {
minVal = nil
}
var logBase *attrValFloat
if opts.YAxis.LogBase >= 2 && opts.YAxis.LogBase <= 1000 {
logBase = &attrValFloat{Val: float64Ptr(opts.YAxis.LogBase)}
}
ax := &cAxs{
AxID: &attrValInt{Val: intPtr(100000001)},
Scaling: &cScaling{
LogBase: logBase,
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
Max: maxVal,
Min: minVal,
},
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])},
Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"),
NumFmt: &cNumFmt{
FormatCode: chartValAxNumFmtFormatCode[opts.Type],
},
MajorTickMark: &attrValString{Val: stringPtr("none")},
MinorTickMark: &attrValString{Val: stringPtr("none")},
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])},
SpPr: f.drawPlotAreaSpPr(),
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
CrossAx: &attrValInt{Val: intPtr(100000000)},
Crosses: &attrValString{Val: stringPtr("autoZero")},
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
}
if numFmt := f.drawChartNumFmt(opts.YAxis.NumFmt); numFmt != nil {
ax.NumFmt = numFmt
}
if opts.YAxis.MajorGridLines {
ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if opts.YAxis.MinorGridLines {
ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
}
if pos, ok := tickLblPosNone[opts.Type]; ok {
ax.TickLblPos.Val = stringPtr(pos)
}
if opts.YAxis.MajorUnit != 0 {
ax.MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)}
}
if opts.order > 0 && opts.YAxis.Secondary && pa.ValAx != nil {
ax.AxID = &attrValInt{Val: intPtr(opts.YAxis.axID)}
ax.AxPos = &attrValString{Val: stringPtr("r")}
ax.Crosses = &attrValString{Val: stringPtr("max")}
ax.CrossAx = &attrValInt{Val: intPtr(opts.XAxis.axID)}
return []*cAxs{pa.ValAx[0], ax}
}
return []*cAxs{ax}
}
// drawPlotAreaSerAx provides a function to draw the c:serAx element.
func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
maxVal := &attrValFloat{Val: opts.YAxis.Maximum}
minVal := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil {
maxVal = nil
}
if opts.YAxis.Minimum == nil {
minVal = nil
}
return []*cAxs{
{
AxID: &attrValInt{Val: intPtr(100000005)},
Scaling: &cScaling{
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
Max: maxVal,
Min: minVal,
},
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])},
SpPr: f.drawPlotAreaSpPr(),
TxPr: f.drawPlotAreaTxPr(nil),
CrossAx: &attrValInt{Val: intPtr(100000001)},
},
}
}
// drawChartFont provides a function to draw the a:rPr element.
func drawChartFont(fnt *Font, r *aRPr) {
if fnt == nil {
return
}
r.B = fnt.Bold
r.I = fnt.Italic
if idx := inStrSlice(supportedDrawingUnderlineTypes, fnt.Underline, true); idx != -1 {
r.U = supportedDrawingUnderlineTypes[idx]
}
if fnt.Color != "" {
if r.SolidFill == nil {
r.SolidFill = &aSolidFill{}
}
r.SolidFill.SchemeClr = nil
r.SolidFill.SrgbClr = &aSrgbClr{Val: stringPtr(strings.ReplaceAll(strings.ToUpper(fnt.Color), "#", ""))}
}
if fnt.Family != "" {
if r.Latin == nil {
r.Latin = &xlsxCTTextFont{}
}
if r.Ea == nil {
r.Ea = &xlsxCTTextFont{}
}
if r.Cs == nil {
r.Cs = &xlsxCTTextFont{}
}
r.Latin.Typeface = fnt.Family
r.Ea.Typeface = fnt.Family
r.Cs.Typeface = fnt.Family
}
if fnt.Size > 0 {
r.Sz = fnt.Size * 100
}
if fnt.Strike {
r.Strike = "sngStrike"
}
}
// drawPlotAreaTitles provides a function to draw the c:title element.
func (f *File) drawPlotAreaTitles(runs []RichTextRun, vert string) *cTitle {
if len(runs) == 0 {
return nil
}
title := &cTitle{Tx: cTx{Rich: &cRich{}}, Overlay: &attrValBool{Val: boolPtr(false)}}
for _, run := range runs {
r := &aR{T: run.Text}
drawChartFont(run.Font, &r.RPr)
title.Tx.Rich.P = append(title.Tx.Rich.P, aP{
PPr: &aPPr{DefRPr: aRPr{}},
R: r,
EndParaRPr: &aEndParaRPr{Lang: "en-US", AltLang: "en-US"},
})
}
if vert == "horz" {
title.Tx.Rich.BodyPr = aBodyPr{Rot: -5400000, Vert: vert}
}
return title
}
// drawPlotAreaDTable provides a function to draw the c:dTable element.
func (f *File) drawPlotAreaDTable(opts *Chart) *cDTable {
if _, ok := plotAreaChartGrouping[opts.Type]; ok && opts.PlotArea.ShowDataTable {
return &cDTable{
ShowHorzBorder: &attrValBool{Val: boolPtr(true)},
ShowVertBorder: &attrValBool{Val: boolPtr(true)},
ShowOutline: &attrValBool{Val: boolPtr(true)},
ShowKeys: &attrValBool{Val: boolPtr(opts.PlotArea.ShowDataTableKeys)},
}
}
return nil
}
// drawPlotAreaSpPr provides a function to draw the c:spPr element.
func (f *File) drawPlotAreaSpPr() *cSpPr {
return &cSpPr{
Ln: &aLn{
W: 9525,
Cap: "flat",
Cmpd: "sng",
Algn: "ctr",
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{Val: intPtr(15000)},
LumOff: &attrValInt{Val: intPtr(85000)},
},
},
},
}
}
// drawPlotAreaTxPr provides a function to draw the c:txPr element.
func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr {
cTxPr := &cTxPr{
BodyPr: aBodyPr{
Rot: -60000000,
SpcFirstLastPara: true,
VertOverflow: "ellipsis",
Vert: "horz",
Wrap: "square",
Anchor: "ctr",
AnchorCtr: true,
},
P: aP{
PPr: &aPPr{
DefRPr: aRPr{
Sz: 900,
B: false,
I: false,
U: "none",
Strike: "noStrike",
Kern: 1200,
Baseline: 0,
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{Val: intPtr(15000)},
LumOff: &attrValInt{Val: intPtr(85000)},
},
},
Latin: &xlsxCTTextFont{Typeface: "+mn-lt"},
Ea: &xlsxCTTextFont{Typeface: "+mn-ea"},
Cs: &xlsxCTTextFont{Typeface: "+mn-cs"},
},
},
EndParaRPr: &aEndParaRPr{Lang: "en-US"},
},
}
if opts != nil {
drawChartFont(&opts.Font, &cTxPr.P.PPr.DefRPr)
if -90 <= opts.Alignment.TextRotation && opts.Alignment.TextRotation <= 90 {
cTxPr.BodyPr.Rot = opts.Alignment.TextRotation * 60000
}
if idx := inStrSlice(supportedDrawingTextVerticalType, opts.Alignment.Vertical, true); idx != -1 {
cTxPr.BodyPr.Vert = supportedDrawingTextVerticalType[idx]
}
}
return cTxPr
}
// drawChartLn provides a function to draw the a:ln element.
func (f *File) drawChartLn(opts *ChartLine) *aLn {
ln := &aLn{
W: f.ptToEMUs(opts.Width),
Cap: "flat",
Cmpd: "sng",
Algn: "ctr",
}
if opts.Dash != ChartDashUnset {
ln.PrstDash = &attrValString{Val: stringPtr(chartDashTypes[opts.Dash])}
}
switch opts.Type {
case ChartLineSolid:
ln.SolidFill = f.drawShapeFill(opts.Fill, &cSpPr{
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{
Val: intPtr(15000),
},
LumOff: &attrValInt{
Val: intPtr(85000),
},
},
},
}).SolidFill
return ln
case ChartLineNone:
ln.NoFill = &attrValString{}
return ln
default:
return nil
}
}
// drawChartLegend provides a function to draw the c:legend element.
func (c *cChart) drawChartLegend(opts *Chart) {
if opts.Legend.Position == "none" {
c.Legend = nil
return
}
if c.Legend == nil {
c.Legend = &cLegend{
LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])},
Overlay: &attrValBool{Val: boolPtr(false)},
}
}
if opts.Legend.Font != nil {
c.Legend.TxPr = &cTxPr{P: aP{PPr: &aPPr{}}}
drawChartFont(opts.Legend.Font, &c.Legend.TxPr.P.PPr.DefRPr)
}
for k := range opts.Series {
font := opts.Series[k].Legend.Font
if font == nil {
continue
}
legendEntry := cLegendEntry{
IDx: &attrValInt{Val: intPtr(k + opts.order)},
TxPr: &cTxPr{P: aP{PPr: &aPPr{}}},
}
drawChartFont(font, &legendEntry.TxPr.P.PPr.DefRPr)
c.Legend.LegendEntry = append(c.Legend.LegendEntry, legendEntry)
}
}
// drawingParser provides a function to parse drawingXML. In order to solve
// the problem that the label structure is changed after serialization and
// deserialization, two different structures: decodeWsDr and encodeWsDr are
// defined.
func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) {
var (
err error
ok bool
)
_, ok = f.Drawings.Load(path)
if !ok {
content := xlsxWsDr{
NS: NameSpaceDrawingMLSpreadSheet.Value,
Xdr: NameSpaceDrawingMLSpreadSheet.Value,
A: NameSpaceDrawingML.Value,
}
if _, ok = f.Pkg.Load(path); ok { // Append Model
decodeWsDr := decodeWsDr{}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&decodeWsDr); err != nil && err != io.EOF {
return nil, 0, err
}
content.R = decodeWsDr.R
for _, v := range decodeWsDr.AlternateContent {
content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{
Content: v.Content,
XMLNSMC: SourceRelationshipCompatibility.Value,
})
}
for _, v := range decodeWsDr.OneCellAnchor {
content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
EditAs: v.EditAs,
GraphicFrame: v.Content,
})
}
for _, v := range decodeWsDr.TwoCellAnchor {
content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{
EditAs: v.EditAs,
GraphicFrame: v.Content,
})
}
}
f.Drawings.Store(path, &content)
}
var wsDr *xlsxWsDr
if drawing, ok := f.Drawings.Load(path); ok && drawing != nil {
wsDr = drawing.(*xlsxWsDr)
}
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2, nil
}
// addDrawingChart provides a function to add chart graphic frame by given
// sheet, drawingXML, cell, width, height, relationship index and format sets.
func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *GraphicOptions) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
width = int(float64(width) * opts.ScaleX)
height = int(float64(height) * opts.ScaleY)
colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = opts.Positioning
from := xlsxFrom{}
from.Col = colStart
from.ColOff = x1 * EMU
from.Row = rowStart
from.RowOff = y1 * EMU
to := xlsxTo{}
to.Col = colEnd
to.ColOff = x2 * EMU
to.Row = rowEnd
to.RowOff = y2 * EMU
twoCellAnchor.From = &from
twoCellAnchor.To = &to
graphicFrame := xlsxGraphicFrame{
NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Name: "Chart " + strconv.Itoa(cNvPrID),
Descr: opts.AltText,
},
},
Graphic: &xlsxGraphic{
GraphicData: &xlsxGraphicData{
URI: NameSpaceDrawingMLChart.Value,
Chart: &xlsxChart{
C: NameSpaceDrawingMLChart.Value,
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(rID),
},
},
},
}
if len(opts.Name) > 0 {
graphicFrame.NvGraphicFramePr.CNvPr.Name = opts.Name
}
graphic, _ := xml.Marshal(graphicFrame)
twoCellAnchor.GraphicFrame = string(graphic)
twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
f.Drawings.Store(drawingXML, content)
return err
}
// addSheetDrawingChart provides a function to add chart graphic frame for
// chartsheet by given sheet, drawingXML, width, height, relationship index
// and format sets.
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *GraphicOptions) error {
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
absoluteAnchor := xdrCellAnchor{
EditAs: opts.Positioning,
Pos: &xlsxPoint2D{},
Ext: &xlsxPositiveSize2D{Cx: 9280533, Cy: 6051719},
}
graphicFrame := xlsxGraphicFrame{
NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Name: "Chart " + strconv.Itoa(cNvPrID),
},
},
Graphic: &xlsxGraphic{
GraphicData: &xlsxGraphicData{
URI: NameSpaceDrawingMLChart.Value,
Chart: &xlsxChart{
C: NameSpaceDrawingMLChart.Value,
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(rID),
},
},
},
}
graphic, _ := xml.Marshal(graphicFrame)
absoluteAnchor.GraphicFrame = string(graphic)
absoluteAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
f.Drawings.Store(drawingXML, content)
return err
}
// deleteDrawing provides a function to delete the chart graphic frame and
// returns deleted embed relationships ID (for unique picture cell anchor) by
// given coordinates and graphic type.
func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) ([]string, error) {
var (
err error
rID string
delRID, refRID []string
rIDMaps = map[string]int{}
wsDr *xlsxWsDr
deCellAnchor *decodeCellAnchor
)
xdrCellAnchorFuncs := map[string]func(anchor *xdrCellAnchor) bool{
"Chart": func(anchor *xdrCellAnchor) bool { return anchor.Pic == nil },
"Pic": func(anchor *xdrCellAnchor) bool { return anchor.Pic != nil },
}
decodeCellAnchorFuncs := map[string]func(anchor *decodeCellAnchor) bool{
"Chart": func(anchor *decodeCellAnchor) bool { return anchor.Pic == nil },
"Pic": func(anchor *decodeCellAnchor) bool { return anchor.Pic != nil },
}
onAnchorCell := func(c, r int) bool { return c == col && r == row }
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return delRID, err
}
deleteCellAnchor := func(ca []*xdrCellAnchor) ([]*xdrCellAnchor, error) {
for idx := 0; idx < len(ca); idx++ {
if err = nil; ca[idx].From != nil && xdrCellAnchorFuncs[drawingType](ca[idx]) {
rID = extractEmbedRID(ca[idx].Pic, nil)
rIDMaps[rID]++
if onAnchorCell(ca[idx].From.Col, ca[idx].From.Row) {
refRID = append(refRID, rID)
ca = append(ca[:idx], ca[idx+1:]...)
idx--
rIDMaps[rID]--
}
continue
}
deCellAnchor = new(decodeCellAnchor)
if err = f.xmlNewDecoder(strings.NewReader("" + ca[idx].GraphicFrame + "")).
Decode(deCellAnchor); err != nil && err != io.EOF {
return ca, err
}
if err = nil; deCellAnchor.From != nil && decodeCellAnchorFuncs[drawingType](deCellAnchor) {
rID = extractEmbedRID(nil, deCellAnchor.Pic)
rIDMaps[rID]++
if onAnchorCell(deCellAnchor.From.Col, deCellAnchor.From.Row) {
refRID = append(refRID, rID)
ca = append(ca[:idx], ca[idx+1:]...)
idx--
rIDMaps[rID]--
}
}
}
return ca, err
}
if wsDr.OneCellAnchor, err = deleteCellAnchor(wsDr.OneCellAnchor); err != nil {
return delRID, err
}
if wsDr.TwoCellAnchor, err = deleteCellAnchor(wsDr.TwoCellAnchor); err != nil {
return delRID, err
}
f.Drawings.Store(drawingXML, wsDr)
return getUnusedCellAnchorRID(delRID, refRID, rIDMaps), err
}
// extractEmbedRID returns embed relationship ID by giving cell anchor.
func extractEmbedRID(pic *xlsxPic, decodePic *decodePic) string {
var rID string
if pic != nil {
rID = pic.BlipFill.Blip.Embed
}
if decodePic != nil {
rID = decodePic.BlipFill.Blip.Embed
}
return rID
}
// getUnusedCellAnchorRID returns relationship ID lists in the cell anchor which
// for remove.
func getUnusedCellAnchorRID(delRID, refRID []string, rIDMaps map[string]int) []string {
for _, rID := range refRID {
if rIDMaps[rID] == 0 && inStrSlice(delRID, rID, false) == -1 {
delRID = append(delRID, rID)
}
}
return delRID
}
// deleteDrawingRels provides a function to delete relationships in
// xl/drawings/_rels/drawings%d.xml.rels by giving drawings relationships path
// and relationship ID.
func (f *File) deleteDrawingRels(rels, rID string) {
drawingRels, _ := f.relsReader(rels)
if drawingRels == nil {
drawingRels = &xlsxRelationships{}
}
drawingRels.mu.Lock()
defer drawingRels.mu.Unlock()
for k, v := range drawingRels.Relationships {
if v.ID == rID {
drawingRels.Relationships = append(drawingRels.Relationships[:k], drawingRels.Relationships[k+1:]...)
}
}
f.Relationships.Store(rels, drawingRels)
}
// genAxID provides a function to generate ID for primary and secondary
// horizontal or vertical axis.
func (f *File) genAxID(opts *Chart) []*attrValInt {
opts.XAxis.axID, opts.YAxis.axID = 100000000, 100000001
if opts.order > 0 && opts.YAxis.Secondary {
opts.XAxis.axID, opts.YAxis.axID = 100000003, 100000004
}
return []*attrValInt{{Val: intPtr(opts.XAxis.axID)}, {Val: intPtr(opts.YAxis.axID)}}
}
================================================
FILE: drawing_test.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDrawingParser(t *testing.T) {
f := File{
Drawings: sync.Map{},
Pkg: sync.Map{},
}
f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(xml.Header+``))
// Test with one cell anchor
_, _, err := f.drawingParser("wsDr")
assert.NoError(t, err)
// Test with unsupported charset
_, _, err = f.drawingParser("charset")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test with alternate content
f.Drawings = sync.Map{}
f.Pkg.Store("wsDr", []byte(xml.Header+``))
_, _, err = f.drawingParser("wsDr")
assert.NoError(t, err)
}
func TestDeleteDrawingRels(t *testing.T) {
f := NewFile()
// Test delete drawing relationships with unsupported charset
rels := "xl/drawings/_rels/drawing1.xml.rels"
f.Relationships.Delete(rels)
f.Pkg.Store(rels, MacintoshCyrillicCharset)
f.deleteDrawingRels(rels, "")
}
================================================
FILE: errors.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"errors"
"fmt"
"strings"
)
var (
// ErrAddVBAProject defined the error message on add the VBA project in
// the workbook.
ErrAddVBAProject = errors.New("unsupported VBA project")
// ErrAttrValBool defined the error message on marshal and unmarshal
// boolean type XML attribute.
ErrAttrValBool = errors.New("unexpected child of attrValBool")
// ErrCellCharsLength defined the error message for receiving a cell
// characters length that exceeds the limit.
ErrCellCharsLength = fmt.Errorf("cell value must be 0-%d characters", TotalCellChars)
// ErrCellStyles defined the error message on cell styles exceeds the limit.
ErrCellStyles = fmt.Errorf("the cell styles exceeds the %d limit", MaxCellStyles)
// ErrColumnNumber defined the error message on receive an invalid column
// number.
ErrColumnNumber = fmt.Errorf("the column number must be greater than or equal to %d and less than or equal to %d", MinColumns, MaxColumns)
// ErrColumnWidth defined the error message on receive an invalid column
// width.
ErrColumnWidth = fmt.Errorf("the width of the column must be less than or equal to %d characters", MaxColumnWidth)
// ErrCoordinates defined the error message on invalid coordinates tuples
// length.
ErrCoordinates = errors.New("coordinates length must be 4")
// ErrCustomNumFmt defined the error message on receive the empty custom
// number format.
ErrCustomNumFmt = errors.New("custom number format can not be empty")
// ErrDataValidationFormulaLength defined the error message for receiving a
// data validation formula length that exceeds the limit.
ErrDataValidationFormulaLength = fmt.Errorf("data validation must be 0-%d characters", MaxFieldLength)
// ErrDataValidationRange defined the error message on set decimal range
// exceeds limit.
ErrDataValidationRange = errors.New("data validation range exceeds limit")
// ErrDefinedNameDuplicate defined the error message on the same name
// already exists on the scope.
ErrDefinedNameDuplicate = errors.New("the same name already exists on the scope")
// ErrDefinedNameScope defined the error message on not found defined name
// in the given scope.
ErrDefinedNameScope = errors.New("no defined name on the scope")
// ErrExistsSheet defined the error message on given sheet already exists.
ErrExistsSheet = errors.New("the same name sheet already exists")
// ErrExistsTableName defined the error message on given table already
// exists.
ErrExistsTableName = errors.New("the same name table already exists")
// ErrFillType defined the error message on receive an invalid fill type.
ErrFillType = errors.New("fill type value must be one of 'gradient' or 'pattern'")
// ErrFillGradientColor defined the error message on receive an invalid fill
// color for 'gradient' type.
ErrFillGradientColor = errors.New("fill color value must be an array of two colors for 'gradient' type")
// ErrFillGradientShading defined the error message on receive an invalid
// fill shading for 'gradient' type.
ErrFillGradientShading = errors.New("fill shading value must be between 0 and 16 for 'gradient' type")
// ErrFillPatternColor defined the error message on receive an invalid fill
// color for 'pattern' type.
ErrFillPatternColor = errors.New("fill color value must be empty or an array of one color for 'pattern' type")
// ErrFillPattern defined the error message on receive an invalid fill
// pattern.
ErrFillPattern = errors.New("fill pattern value must be between 0 and 18")
// ErrFontLength defined the error message on the length of the font
// family name overflow.
ErrFontLength = fmt.Errorf("the length of the font family name must be less than or equal to %d", MaxFontFamilyLength)
// ErrFontSize defined the error message on the size of the font is invalid.
ErrFontSize = fmt.Errorf("font size must be an integer from %d to %d points", MinFontSize, MaxFontSize)
// ErrFormControlValue defined the error message for receiving a scroll
// value exceeds limit.
ErrFormControlValue = fmt.Errorf("scroll value must be an integer from 0 to %d", MaxFormControlValue)
// ErrGroupSheets defined the error message on group sheets.
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
// ErrImgExt defined the error message on receive an unsupported image
// extension.
ErrImgExt = errors.New("unsupported image extension")
// ErrInvalidFormula defined the error message on receive an invalid
// formula.
ErrInvalidFormula = errors.New("formula not valid")
// ErrMaxFilePathLength defined the error message on receive the file path
// length overflow.
ErrMaxFilePathLength = fmt.Errorf("file path length exceeds maximum limit %d characters", MaxFilePathLength)
// ErrMaxRowHeight defined the error message on receive an invalid row
// height.
ErrMaxRowHeight = fmt.Errorf("the height of the row must be less than or equal to %d points", MaxRowHeight)
// ErrMaxRows defined the error message on receive a row number exceeds
// maximum limit.
ErrMaxRows = errors.New("row number exceeds maximum limit")
// ErrNameLength defined the error message on receiving the defined name or
// table name length exceeds the limit.
ErrNameLength = fmt.Errorf("the name length exceeds the %d characters limit", MaxFieldLength)
// ErrMaxGraphicAltTextLength defined the error message on receiving the
// graphic alt text length exceeds the limit.
ErrMaxGraphicAltTextLength = fmt.Errorf("the alt text length exceeds the %d characters limit", MaxGraphicAltTextLength)
// ErrMaxGraphicNameLength defined the error message on receiving the
// graphic name length exceeds the limit.
ErrMaxGraphicNameLength = fmt.Errorf("the name length exceeds the %d characters limit", MaxGraphicNameLength)
// ErrOptionsUnzipSizeLimit defined the error message for receiving
// invalid UnzipSizeLimit and UnzipXMLSizeLimit.
ErrOptionsUnzipSizeLimit = errors.New("the value of UnzipSizeLimit should be greater than or equal to UnzipXMLSizeLimit")
// ErrOutlineLevel defined the error message on receive an invalid outline
// level number.
ErrOutlineLevel = errors.New("invalid outline level")
// ErrPageSetupAdjustTo defined the error message for receiving a page setup
// adjust to value exceeds limit.
ErrPageSetupAdjustTo = errors.New("adjust to value must be an integer from 0 to 400")
// ErrParameterInvalid defined the error message on receive the invalid
// parameter.
ErrParameterInvalid = errors.New("parameter is invalid")
// ErrParameterRequired defined the error message on receive the empty
// parameter.
ErrParameterRequired = errors.New("parameter is required")
// ErrPasswordLengthInvalid defined the error message on invalid password
// length.
ErrPasswordLengthInvalid = errors.New("password length invalid")
// ErrPivotTableClassicLayout defined the error message on enable
// ClassicLayout and CompactData in the same time.
ErrPivotTableClassicLayout = errors.New("cannot enable ClassicLayout and CompactData in the same time")
// ErrSave defined the error message for saving file.
ErrSave = errors.New("no path defined for file, consider File.WriteTo or File.Write")
// ErrSheetIdx defined the error message on receive the invalid worksheet
// index.
ErrSheetIdx = errors.New("invalid worksheet index")
// ErrSheetNameBlank defined the error message on receive the blank sheet
// name.
ErrSheetNameBlank = errors.New("the sheet name can not be blank")
// ErrSheetNameInvalid defined the error message on receive the sheet name
// contains invalid characters.
ErrSheetNameInvalid = errors.New("the sheet can not contain any of the characters :\\/?*[or]")
// ErrSheetNameLength defined the error message on receiving the sheet
// name length exceeds the limit.
ErrSheetNameLength = fmt.Errorf("the sheet name length exceeds the %d characters limit", MaxSheetNameLength)
// ErrSheetNameSingleQuote defined the error message on the first or last
// character of the sheet name was a single quote.
ErrSheetNameSingleQuote = errors.New("the first or last character of the sheet name can not be a single quote")
// ErrSparkline defined the error message on receive the invalid sparkline
// parameters.
ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters")
// ErrSparklineLocation defined the error message on missing Location
// parameters
ErrSparklineLocation = errors.New("parameter 'Location' is required")
// ErrSparklineRange defined the error message on missing sparkline Range
// parameters
ErrSparklineRange = errors.New("parameter 'Range' is required")
// ErrSparklineStyle defined the error message on receive the invalid
// sparkline Style parameters.
ErrSparklineStyle = errors.New("parameter 'Style' value must be an integer from 0 to 35")
// ErrSparklineType defined the error message on receive the invalid
// sparkline Type parameters.
ErrSparklineType = errors.New("parameter 'Type' value must be one of 'line', 'column' or 'win_loss'")
// ErrTotalSheetHyperlinks defined the error message on hyperlinks count
// overflow.
ErrTotalSheetHyperlinks = errors.New("over maximum limit hyperlinks in a worksheet")
// ErrTransparency defined the error message for receiving a transparency
// value exceeds limit.
ErrTransparency = errors.New("transparency value must be an integer from 0 to 100")
// ErrUnknownEncryptMechanism defined the error message on unsupported
// encryption mechanism.
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
// ErrUnprotectSheet defined the error message on worksheet has set no
// protection.
ErrUnprotectSheet = errors.New("worksheet has set no protect")
// ErrUnprotectSheetPassword defined the error message on remove sheet
// protection with password verification failed.
ErrUnprotectSheetPassword = errors.New("worksheet protect password not match")
// ErrUnprotectWorkbook defined the error message on workbook has set no
// protection.
ErrUnprotectWorkbook = errors.New("workbook has set no protect")
// ErrUnprotectWorkbookPassword defined the error message on remove workbook
// protection with password verification failed.
ErrUnprotectWorkbookPassword = errors.New("workbook protect password not match")
// ErrUnsupportedEncryptMechanism defined the error message on unsupported
// encryption mechanism.
ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism")
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
// hash algorithm.
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
// ErrUnsupportedNumberFormat defined the error message on unsupported
// number format expression.
ErrUnsupportedNumberFormat = errors.New("unsupported number format token")
// ErrWorkbookFileFormat defined the error message on receive an
// unsupported workbook file format.
ErrWorkbookFileFormat = errors.New("unsupported workbook file format")
// ErrWorkbookPassword defined the error message on receiving the incorrect
// workbook password.
ErrWorkbookPassword = errors.New("the supplied open workbook password is not correct")
)
// ErrSheetNotExist defined an error of sheet that does not exist.
type ErrSheetNotExist struct {
SheetName string
}
// Error returns the error message on receiving the non existing sheet name.
func (err ErrSheetNotExist) Error() string {
return fmt.Sprintf("sheet %s does not exist", err.SheetName)
}
// newAddCommentError defined the error message on the comment already exist in
// the cell.
func newAddCommentError(cell string) error {
return fmt.Errorf("comment already exist on cell %s", cell)
}
// newCellNameToCoordinatesError defined the error message on converts
// alphanumeric cell name to coordinates.
func newCellNameToCoordinatesError(cell string, err error) error {
return fmt.Errorf("cannot convert cell %q to coordinates: %v", cell, err)
}
// newCoordinatesToCellNameError defined the error message on converts [X, Y]
// coordinates to alpha-numeric cell name.
func newCoordinatesToCellNameError(col, row int) error {
return fmt.Errorf("invalid cell reference [%d, %d]", col, row)
}
// newFieldLengthError defined the error message on receiving the field length
// overflow.
func newFieldLengthError(name string) error {
return fmt.Errorf("field %s must be less than or equal to 255 characters", name)
}
// newInvalidAutoFilterColumnError defined the error message on receiving the
// incorrect index of column.
func newInvalidAutoFilterColumnError(col string) error {
return fmt.Errorf("incorrect index of column %q", col)
}
// newInvalidAutoFilterExpError defined the error message on receiving the
// incorrect number of tokens in criteria expression.
func newInvalidAutoFilterExpError(exp string) error {
return fmt.Errorf("incorrect number of tokens in criteria %q", exp)
}
// newInvalidAutoFilterOperatorError defined the error message on receiving the
// incorrect expression operator.
func newInvalidAutoFilterOperatorError(op, exp string) error {
return fmt.Errorf("the operator %q in expression %q is not valid in relation to Blanks/NonBlanks", op, exp)
}
// newInvalidCellNameError defined the error message on receiving the invalid
// cell name.
func newInvalidCellNameError(cell string) error {
return fmt.Errorf("invalid cell name %q", cell)
}
// newInvalidColumnNameError defined the error message on receiving the
// invalid column name.
func newInvalidColumnNameError(col string) error {
return fmt.Errorf("invalid column name %q", col)
}
// newInvalidExcelDateError defined the error message on receiving the data
// with negative values.
func newInvalidExcelDateError(dateValue float64) error {
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
}
// newInvalidLinkTypeError defined the error message on receiving the invalid
// hyper link type.
func newInvalidLinkTypeError(linkType string) error {
return fmt.Errorf("invalid link type %q", linkType)
}
// newInvalidNameError defined the error message on receiving the invalid
// defined name or table name.
func newInvalidNameError(name string) error {
return fmt.Errorf("invalid name %q, the name should be starts with a letter or underscore, can not include a space or character, and can not conflict with an existing name in the workbook", name)
}
// newInvalidOptionalValue defined the error message on receiving the invalid
// optional value.
func newInvalidOptionalValue(name, value string, values []string) error {
return fmt.Errorf("invalid %s value %q, acceptable value should be one of %s", name, value, strings.Join(values, ", "))
}
// newInvalidRowNumberError defined the error message on receiving the invalid
// row number.
func newInvalidRowNumberError(row int) error {
return fmt.Errorf("invalid row number %d", row)
}
// newInvalidSlicerNameError defined the error message on receiving the invalid
// slicer name.
func newInvalidSlicerNameError(name string) error {
return fmt.Errorf("invalid slicer name %q", name)
}
// newInvalidStyleID defined the error message on receiving the invalid style
// ID.
func newInvalidStyleID(styleID int) error {
return fmt.Errorf("invalid style ID %d", styleID)
}
// newNoExistSlicerError defined the error message on receiving the non existing
// slicer name.
func newNoExistSlicerError(name string) error {
return fmt.Errorf("slicer %s does not exist", name)
}
// newNoExistTableError defined the error message on receiving the non existing
// table name.
func newNoExistTableError(name string) error {
return fmt.Errorf("table %s does not exist", name)
}
// newNotWorksheetError defined the error message on receiving a sheet which
// not a worksheet.
func newNotWorksheetError(name string) error {
return fmt.Errorf("sheet %s is not a worksheet", name)
}
// newPivotTableColFieldsError defined the error message on same data field
// appears both in the pivot table column fields and filter fields.
func newPivotTableColFieldsError(data []string) error {
return fmt.Errorf("data fields %s appear both in the pivot table column fields and filter fields", strings.Join(data, ", "))
}
// newPivotTableRowFieldsError defined the error message on same data field
// appears both in the pivot table row fields and filter fields.
func newPivotTableRowFieldsError(data []string) error {
return fmt.Errorf("data fields %s appear both in the pivot table row fields and filter fields", strings.Join(data, ", "))
}
// newPivotTableDataRangeError defined the error message on receiving the
// invalid pivot table data range.
func newPivotTableDataRangeError(msg string) error {
return fmt.Errorf("parameter 'DataRange' parsing error: %s", msg)
}
// newPivotTableRangeError defined the error message on receiving the invalid
// pivot table range.
func newPivotTableRangeError(msg string) error {
return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", msg)
}
// newStreamSetRowError defined the error message on the stream writer
// receiving the non-ascending row number.
func newStreamSetRowError(row int) error {
return fmt.Errorf("row %d has already been written", row)
}
// newStreamSetRowOrderError defined the error message on calling the SetRow
// function before the order function.
func newStreamSetRowOrderError(name string) error {
return fmt.Errorf("must call the %s function before the SetRow function", name)
}
// newUnknownFilterTokenError defined the error message on receiving a unknown
// filter operator token.
func newUnknownFilterTokenError(token string) error {
return fmt.Errorf("unknown operator: %s", token)
}
// newUnsupportedChartType defined the error message on receiving the chart
// type are unsupported.
func newUnsupportedChartType(chartType ChartType) error {
return fmt.Errorf("unsupported chart type %d", chartType)
}
// newUnsupportedPivotCacheSourceType defined the error message on receiving the
// source type of pivot table cache.
func newUnsupportedPivotCacheSourceType(sourceType string) error {
return fmt.Errorf("unsupported pivot table cache source type: %s", sourceType)
}
// newUnzipSizeLimitError defined the error message on unzip size exceeds the
// limit.
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
}
// newViewIdxError defined the error message on receiving a invalid sheet view
// index.
func newViewIdxError(viewIndex int) error {
return fmt.Errorf("view index %d out of range", viewIndex)
}
================================================
FILE: errors_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewInvalidColNameError(t *testing.T) {
assert.EqualError(t, newInvalidColumnNameError("A"), "invalid column name \"A\"")
assert.EqualError(t, newInvalidColumnNameError(""), "invalid column name \"\"")
}
func TestNewInvalidRowNumberError(t *testing.T) {
assert.EqualError(t, newInvalidRowNumberError(0), "invalid row number 0")
}
func TestNewInvalidCellNameError(t *testing.T) {
assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"")
assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"")
}
func TestNewInvalidExcelDateError(t *testing.T) {
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported")
}
================================================
FILE: excelize.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
//
// See https://xuri.me/excelize for more information about this package.
package excelize
import (
"archive/zip"
"bytes"
"encoding/xml"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"golang.org/x/net/html/charset"
)
// File define a populated spreadsheet file struct.
type File struct {
mu sync.Mutex
checked sync.Map
formulaChecked bool
zip64Entries []string
options *Options
sharedStringItem [][]uint
sharedStringsMap map[string]int
sharedStringTemp *os.File
sheetMap map[string]string
streams map[string]*StreamWriter
tempFiles sync.Map
xmlAttr sync.Map
calcCache sync.Map
formulaArgCache sync.Map
CalcChain *xlsxCalcChain
CharsetReader func(charset string, input io.Reader) (rdr io.Reader, err error)
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
DecodeVMLDrawing map[string]*decodeVmlDrawing
DecodeCellImages *decodeCellImages
Drawings sync.Map
Path string
Pkg sync.Map
Relationships sync.Map
SharedStrings *xlsxSST
Sheet sync.Map
SheetCount int
Styles *xlsxStyleSheet
Theme *decodeTheme
VMLDrawing map[string]*vmlDrawing
VolatileDeps *xlsxVolTypes
WorkBook *xlsxWorkbook
ZipWriter func(io.Writer) ZipWriter
}
// ZipWriter defines an interface for writing files to a ZIP archive. It
// provides methods to create new files within the archive, add files from a
// filesystem, and close the archive when writing is complete.
type ZipWriter interface {
Create(name string) (io.Writer, error)
AddFS(fsys fs.FS) error
Close() error
}
// Options define the options for opening and reading the spreadsheet.
//
// MaxCalcIterations specifies the maximum iterations for iterative
// calculation, the default value is 0.
//
// Password specifies the password of the spreadsheet in plain text.
//
// RawCellValue specifies if apply the number format for the cell value or get
// the raw value.
//
// UnzipSizeLimit specifies to unzip size limit in bytes on open the
// spreadsheet, this value should be greater than or equal to
// UnzipXMLSizeLimit, the default size limit is 16GB.
//
// UnzipXMLSizeLimit specifies the memory limit on unzipping worksheet and
// shared string table in bytes, worksheet XML will be extracted to system
// temporary directory when the file size is over this value, this value
// should be less than or equal to UnzipSizeLimit, the default value is
// 16MB.
//
// TmpDir specifies the temporary directory for creating temporary files, if the
// value is empty, the system default temporary directory will be used.
//
// ShortDatePattern specifies the short date number format code. In the
// spreadsheet applications, date formats display date and time serial numbers
// as date values. Date formats that begin with an asterisk (*) respond to
// changes in regional date and time settings that are specified for the
// operating system. Formats without an asterisk are not affected by operating
// system settings. The ShortDatePattern used for specifies apply date formats
// that begin with an asterisk.
//
// LongDatePattern specifies the long date number format code.
//
// LongTimePattern specifies the long time number format code.
//
// CultureInfo specifies the country code for applying built-in language number
// format code these effect by the system's local language settings.
type Options struct {
MaxCalcIterations uint
Password string
RawCellValue bool
UnzipSizeLimit int64
UnzipXMLSizeLimit int64
TmpDir string
ShortDatePattern string
LongDatePattern string
LongTimePattern string
CultureInfo CultureName
}
// OpenFile take the name of a spreadsheet file and returns a populated
// spreadsheet file struct for it. For example, open spreadsheet with
// password protection:
//
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
//
// Close the file by Close function after opening the spreadsheet.
func OpenFile(filename string, opts ...Options) (*File, error) {
file, err := os.Open(filepath.Clean(filename))
if err != nil {
return nil, err
}
f, err := OpenReader(file, opts...)
if err != nil {
if closeErr := file.Close(); closeErr != nil {
return f, closeErr
}
return f, err
}
f.Path = filename
return f, file.Close()
}
// newFile is object builder
func newFile() *File {
return &File{
options: &Options{UnzipSizeLimit: UnzipSizeLimit, UnzipXMLSizeLimit: StreamChunkSize},
xmlAttr: sync.Map{},
checked: sync.Map{},
sheetMap: make(map[string]string),
tempFiles: sync.Map{},
Comments: make(map[string]*xlsxComments),
Drawings: sync.Map{},
sharedStringsMap: make(map[string]int),
Sheet: sync.Map{},
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
VMLDrawing: make(map[string]*vmlDrawing),
Relationships: sync.Map{},
CharsetReader: charset.NewReaderLabel,
ZipWriter: func(w io.Writer) ZipWriter { return zip.NewWriter(w) },
}
}
// checkOpenReaderOptions check and validate options field value for open
// reader.
func (f *File) checkOpenReaderOptions() error {
if f.options.UnzipSizeLimit == 0 {
f.options.UnzipSizeLimit = UnzipSizeLimit
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
f.options.UnzipSizeLimit = f.options.UnzipXMLSizeLimit
}
}
if f.options.UnzipXMLSizeLimit == 0 {
f.options.UnzipXMLSizeLimit = StreamChunkSize
if f.options.UnzipSizeLimit < f.options.UnzipXMLSizeLimit {
f.options.UnzipXMLSizeLimit = f.options.UnzipSizeLimit
}
}
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
return ErrOptionsUnzipSizeLimit
}
return f.checkDateTimePattern()
}
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
func OpenReader(r io.Reader, opts ...Options) (*File, error) {
b, err := io.ReadAll(r)
if err != nil {
return nil, err
}
f := newFile()
f.options = f.getOptions(opts...)
if err = f.checkOpenReaderOptions(); err != nil {
return nil, err
}
if bytes.Contains(b, oleIdentifier) {
if b, err = Decrypt(b, f.options); err != nil {
return nil, ErrWorkbookFileFormat
}
}
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
if err != nil {
if len(f.options.Password) > 0 {
return nil, ErrWorkbookPassword
}
return nil, err
}
file, sheetCount, err := f.ReadZipReader(zr)
if err != nil {
return nil, err
}
f.SheetCount = sheetCount
for k, v := range file {
f.Pkg.Store(k, v)
}
if f.CalcChain, err = f.calcChainReader(); err != nil {
return f, err
}
if f.sheetMap, err = f.getSheetMap(); err != nil {
return f, err
}
if f.Styles, err = f.stylesReader(); err != nil {
return f, err
}
f.Theme, err = f.themeReader()
return f, err
}
// getOptions provides a function to parse the optional settings for open
// and reading spreadsheet.
func (f *File) getOptions(opts ...Options) *Options {
options := f.options
for _, opt := range opts {
options = &opt
}
return options
}
// CharsetTranscoder set user defined codepage transcoder function for open
// workbook from non UTF-8 encoding.
func (f *File) CharsetTranscoder(fn func(charset string, input io.Reader) (rdr io.Reader, err error)) *File {
f.CharsetReader = fn
return f
}
// SetZipWriter set user defined zip writer function for saving the workbook.
func (f *File) SetZipWriter(fn func(io.Writer) ZipWriter) *File { f.ZipWriter = fn; return f }
// Creates new XML decoder with charset reader.
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
ret = xml.NewDecoder(rdr)
ret.CharsetReader = f.CharsetReader
return
}
// setDefaultTimeStyle provides a function to set default numbers format for
// time.Time type cell value by given worksheet name, cell reference and
// number format code.
func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error {
styleIdx, err := f.GetCellStyle(sheet, cell)
if err != nil {
return err
}
if styleIdx == 0 {
styleIdx, _ = f.NewStyle(&Style{NumFmt: format})
} else {
style, _ := f.GetStyle(styleIdx)
style.NumFmt = format
styleIdx, _ = f.NewStyle(style)
}
return f.SetCellStyle(sheet, cell, cell, styleIdx)
}
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
var (
name string
ok bool
)
if err = checkSheetName(sheet); err != nil {
return
}
if name, ok = f.getSheetXMLPath(sheet); !ok {
err = ErrSheetNotExist{sheet}
return
}
if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
ws = worksheet.(*xlsxWorksheet)
return
}
for _, sheetType := range []string{"xl/chartsheets", "xl/dialogsheet", "xl/macrosheet"} {
if strings.HasPrefix(name, sheetType) {
err = newNotWorksheetError(sheet)
return
}
}
ws = new(xlsxWorksheet)
if attrs, ok := f.xmlAttr.Load(name); !ok {
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name))))
if attrs == nil {
attrs = []xml.Attr{}
}
attrs = append(attrs.([]xml.Attr), getRootElement(d)...)
f.xmlAttr.Store(name, attrs)
}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))).
Decode(ws); err != nil && err != io.EOF {
return
}
err = nil
if _, ok = f.checked.Load(name); !ok {
ws.checkSheet()
if err = ws.checkRow(); err != nil {
return
}
f.checked.Store(name, true)
}
f.Sheet.Store(name, ws)
return
}
// checkSheet provides a function to fill each row element and make that is
// continuous in a worksheet of XML.
func (ws *xlsxWorksheet) checkSheet() {
var (
row int
r0Rows []xlsxRow
lastRowNum = func(r xlsxRow) int {
var num int
for _, cell := range r.C {
if _, row, err := CellNameToCoordinates(cell.R); err == nil {
if row > num {
num = row
}
}
}
return num
}
)
for i := 0; i < len(ws.SheetData.Row); i++ {
r := ws.SheetData.Row[i]
if r.R == 0 || r.R == row {
num := lastRowNum(r)
if num > row {
row = num
}
if num == 0 {
row++
}
r.R = row
r0Rows = append(r0Rows, r)
ws.SheetData.Row = append(ws.SheetData.Row[:i], ws.SheetData.Row[i+1:]...)
i--
continue
}
if r.R != 0 && r.R > row {
row = r.R
}
}
sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
row = 0
for _, r := range ws.SheetData.Row {
if r.R != 0 {
sheetData.Row[r.R-1] = r
row = r.R
}
}
for _, r0Row := range r0Rows {
sheetData.Row[r0Row.R-1].R = r0Row.R
ws.checkSheetR0(&sheetData, &r0Row, true)
}
for i := 1; i <= row; i++ {
sheetData.Row[i-1].R = i
ws.checkSheetR0(&sheetData, &sheetData.Row[i-1], false)
}
}
// checkSheetR0 handle the row element with r="0" attribute, cells in this row
// could be disorderly, the cell in this row can be used as the value of
// which cell is empty in the normal rows.
func (ws *xlsxWorksheet) checkSheetR0(sheetData *xlsxSheetData, rowData *xlsxRow, r0 bool) {
checkRow := func(col, row int, r0 bool, cell xlsxC) {
rowIdx := row - 1
columns, colIdx := len(sheetData.Row[rowIdx].C), col-1
for c := columns; c < col; c++ {
sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{})
}
if !sheetData.Row[rowIdx].C[colIdx].hasValue() {
sheetData.Row[rowIdx].C[colIdx] = cell
}
if r0 {
sheetData.Row[rowIdx].C[colIdx] = cell
}
}
var err error
for i, cell := range rowData.C {
col, row := i+1, rowData.R
if cell.R == "" {
checkRow(col, row, r0, cell)
continue
}
if col, row, err = CellNameToCoordinates(cell.R); err == nil && r0 {
checkRow(col, row, r0, cell)
}
}
ws.SheetData = *sheetData
}
// setRels provides a function to set relationships by given relationship ID,
// XML path, relationship type, target and target mode.
func (f *File) setRels(rID, relPath, relType, target, targetMode string) int {
rels, _ := f.relsReader(relPath)
if rels == nil || rID == "" {
return f.addRels(relPath, relType, target, targetMode)
}
rels.mu.Lock()
defer rels.mu.Unlock()
var ID int
for i, rel := range rels.Relationships {
if rel.ID == rID {
rels.Relationships[i].Type = relType
rels.Relationships[i].Target = target
rels.Relationships[i].TargetMode = targetMode
ID, _ = strconv.Atoi(strings.TrimPrefix(rID, "rId"))
break
}
}
return ID
}
// addRels provides a function to add relationships by given XML path,
// relationship type, target and target mode.
func (f *File) addRels(relPath, relType, target, targetMode string) int {
uniqPart := map[string]string{
SourceRelationshipCustomProperties: "/docProps/custom.xml",
SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
}
rels, _ := f.relsReader(relPath)
if rels == nil {
rels = &xlsxRelationships{}
}
rels.mu.Lock()
defer rels.mu.Unlock()
var rID int
for idx, rel := range rels.Relationships {
ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
if ID > rID {
rID = ID
}
if relType == rel.Type {
if partName, ok := uniqPart[rel.Type]; ok {
rels.Relationships[idx].Target = partName
return rID
}
}
}
rID++
var ID bytes.Buffer
ID.WriteString("rId")
ID.WriteString(strconv.Itoa(rID))
rels.Relationships = append(rels.Relationships, xlsxRelationship{
ID: ID.String(),
Type: relType,
Target: target,
TargetMode: targetMode,
})
f.Relationships.Store(relPath, rels)
return rID
}
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel application. This function will be remove value tag when met a
// cell have a linked value. Reference
// https://learn.microsoft.com/en-us/archive/msdn-technet-forums/e16bae1f-6a2c-4325-8013-e989a3479066
//
// Notice: after opening generated workbook, Excel will update the linked value
// and generate a new value and will prompt to save the file or not.
//
// For example:
//
//
//
// SUM(Sheet2!D2,Sheet2!D11)
// 100
//
//
//
// to
//
//
//
// SUM(Sheet2!D2,Sheet2!D11)
//
//
func (f *File) UpdateLinkedValue() error {
wb, err := f.workbookReader()
if err != nil {
return err
}
// recalculate formulas
wb.CalcPr = nil
for _, name := range f.GetSheetList() {
ws, err := f.workSheetReader(name)
if err != nil {
if err.Error() == newNotWorksheetError(name).Error() {
continue
}
return err
}
for indexR := range ws.SheetData.Row {
for indexC, col := range ws.SheetData.Row[indexR].C {
if col.F != nil && col.V != "" {
ws.SheetData.Row[indexR].C[indexC].V = ""
ws.SheetData.Row[indexR].C[indexC].T = ""
}
}
}
}
return nil
}
// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be XLSM or XLTM. For
// example:
//
// codeName := "Sheet1"
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// CodeName: &codeName,
// }); err != nil {
// fmt.Println(err)
// return
// }
// file, err := os.ReadFile("vbaProject.bin")
// if err != nil {
// fmt.Println(err)
// return
// }
// if err := f.AddVBAProject(file); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("macros.xlsm"); err != nil {
// fmt.Println(err)
// return
// }
func (f *File) AddVBAProject(file []byte) error {
var err error
// Check vbaProject.bin exists first.
if !bytes.Contains(file, oleIdentifier) {
return ErrAddVBAProject
}
rels, err := f.relsReader(f.getWorkbookRelsPath())
if err != nil {
return err
}
rels.mu.Lock()
defer rels.mu.Unlock()
var rID int
var ok bool
for _, rel := range rels.Relationships {
if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
ok = true
continue
}
t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
if t > rID {
rID = t
}
}
rID++
if !ok {
rels.Relationships = append(rels.Relationships, xlsxRelationship{
ID: "rId" + strconv.Itoa(rID),
Target: "vbaProject.bin",
Type: SourceRelationshipVBAProject,
})
}
f.Pkg.Store("xl/vbaProject.bin", file)
return err
}
// setContentTypePartProjectExtensions provides a function to set the content
// type for relationship parts and the main document part.
func (f *File) setContentTypePartProjectExtensions(contentType string) error {
var ok bool
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for _, v := range content.Defaults {
if v.Extension == "bin" {
ok = true
}
}
for idx, o := range content.Overrides {
if o.PartName == "/xl/workbook.xml" {
content.Overrides[idx].ContentType = contentType
}
}
if !ok {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: "bin",
ContentType: ContentTypeVBA,
})
}
return err
}
// metadataReader provides a function to get the pointer to the structure
// after deserialization of xl/metadata.xml.
func (f *File) metadataReader() (*xlsxMetadata, error) {
var metaData xlsxMetadata
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))).
Decode(&metaData); err != nil && err != io.EOF {
return &metaData, err
}
return &metaData, nil
}
// richValueReader provides a function to get the pointer to the structure after
// deserialization of xl/richData/richvalue.xml.
func (f *File) richValueReader() (*xlsxRichValueData, error) {
var richValue xlsxRichValueData
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValue)))).
Decode(&richValue); err != nil && err != io.EOF {
return &richValue, err
}
return &richValue, nil
}
// richValueRelReader provides a function to get the pointer to the structure
// after deserialization of xl/richData/richValueRel.xml.
func (f *File) richValueRelReader() (*xlsxRichValueRels, error) {
var richValueRels xlsxRichValueRels
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueRel)))).
Decode(&richValueRels); err != nil && err != io.EOF {
return &richValueRels, err
}
return &richValueRels, nil
}
// richValueStructuresReader provides a function to get the pointer to the structure after
// deserialization of xl/richData/rdrichvaluestructure.xml.
func (f *File) richValueStructuresReader() (*xlsxRichValueStructures, error) {
var richValueStructures xlsxRichValueStructures
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueStructure)))).
Decode(&richValueStructures); err != nil && err != io.EOF {
return &richValueStructures, err
}
return &richValueStructures, nil
}
// richValueWebImageReader provides a function to get the pointer to the
// structure after deserialization of xl/richData/rdRichValueWebImage.xml.
func (f *File) richValueWebImageReader() (*xlsxWebImagesSupportingRichData, error) {
var richValueWebImages xlsxWebImagesSupportingRichData
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueWebImage)))).
Decode(&richValueWebImages); err != nil && err != io.EOF {
return &richValueWebImages, err
}
return &richValueWebImages, nil
}
// getRichDataRichValueRelRelationships provides a function to get relationships
// from xl/richData/_rels/richValueRel.xml.rels by given relationship ID.
func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationship {
if rels, _ := f.relsReader(defaultXMLRdRichValueRelRels); rels != nil {
rels.mu.Lock()
defer rels.mu.Unlock()
for _, v := range rels.Relationships {
if v.ID == rID {
return &v
}
}
}
return nil
}
// getRichValueWebImageRelationships provides a function to get relationships
// from xl/richData/_rels/rdRichValueWebImage.xml.rels by given relationship ID.
func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship {
if rels, _ := f.relsReader(defaultXMLRdRichValueWebImageRels); rels != nil {
rels.mu.Lock()
defer rels.mu.Unlock()
for _, v := range rels.Relationships {
if v.ID == rID {
return &v
}
}
}
return nil
}
================================================
FILE: excelize_test.go
================================================
package excelize
import (
"archive/zip"
"bytes"
"compress/gzip"
"encoding/xml"
"fmt"
"image/color"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/net/html/charset"
)
func TestOpenFile(t *testing.T) {
// Test update the spreadsheet file
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test get all the rows in a not exists worksheet
_, err = f.GetRows("Sheet4")
assert.EqualError(t, err, "sheet Sheet4 does not exist")
// Test get all the rows with invalid sheet name
_, err = f.GetRows("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test get all the rows in a worksheet
rows, err := f.GetRows("Sheet2")
expected := [][]string{
{"Monitor", "", "Brand", "", "inlineStr"},
{"> 23 Inch", "19", "HP", "200"},
{"20-23 Inch", "24", "DELL", "450"},
{"17-20 Inch", "56", "Lenove", "200"},
{"< 17 Inch", "21", "SONY", "510"},
{"", "", "Acer", "315"},
{"", "", "IBM", "127"},
{"", "", "ASUS", "89"},
{"", "", "Apple", "348"},
{"", "", "SAMSUNG", "53"},
{"", "", "Other", "37", "", "", "", "", ""},
}
assert.NoError(t, err)
assert.Equal(t, expected, rows)
assert.NoError(t, f.UpdateLinkedValue())
assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(100.1588, 'f', -1, 32)))
assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(-100.1588, 'f', -1, 64)))
// Test set cell value with invalid sheet name
assert.EqualError(t, f.SetCellDefault("Sheet:1", "A1", ""), ErrSheetNameInvalid.Error())
// Test set cell value with illegal row number
assert.EqualError(t, f.SetCellDefault("Sheet2", "A", strconv.FormatFloat(-100.1588, 'f', -1, 64)),
newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, f.SetCellInt("Sheet2", "A1", 100))
// Test set cell integer value with illegal row number
assert.EqualError(t, f.SetCellInt("Sheet2", "A", 100), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set cell integer value with invalid sheet name
assert.EqualError(t, f.SetCellInt("Sheet:1", "A1", 100), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns"))
// Test max characters in a cell
assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2)))
_, err = f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.")
assert.EqualError(t, err, ErrSheetNameLength.Error())
// Test set worksheet name with illegal name
assert.EqualError(t, f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title."), ErrSheetNameLength.Error())
assert.EqualError(t, f.SetCellInt("Sheet3", "A23", 10), "sheet Sheet3 does not exist")
assert.EqualError(t, f.SetCellStr("Sheet3", "b230", "10"), "sheet Sheet3 does not exist")
assert.EqualError(t, f.SetCellStr("Sheet10", "b230", "10"), "sheet Sheet10 does not exist")
// Test set cell string data type value with invalid sheet name
assert.EqualError(t, f.SetCellStr("Sheet:1", "A1", "1"), ErrSheetNameInvalid.Error())
// Test set cell string value with illegal row number
assert.EqualError(t, f.SetCellStr("Sheet1", "A", "10"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
f.SetActiveSheet(2)
// Test get cell formula with given rows number
formula, err := f.GetCellFormula("Sheet1", "B19")
assert.NoError(t, err)
assert.Equal(t, "SUM(Sheet2!D2,Sheet2!D11)", formula)
// Test get cell formula with illegal worksheet name
formula, err = f.GetCellFormula("Sheet2", "B20")
assert.NoError(t, err)
assert.Empty(t, formula)
// Test get cell formula with illegal rows number
_, err = f.GetCellFormula("Sheet1", "B")
assert.EqualError(t, err, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test get shared cell formula
_, err = f.GetCellFormula("Sheet2", "H11")
assert.NoError(t, err)
_, err = f.GetCellFormula("Sheet2", "I11")
assert.NoError(t, err)
_, err = getSharedFormula(&xlsxWorksheet{}, 0, "")
assert.NoError(t, err)
// Test read cell value with given illegal rows number
_, err = f.GetCellValue("Sheet2", "a-1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error())
_, err = f.GetCellValue("Sheet2", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test read cell value with given lowercase column number
_, err = f.GetCellValue("Sheet2", "a5")
assert.NoError(t, err)
_, err = f.GetCellValue("Sheet2", "C11")
assert.NoError(t, err)
_, err = f.GetCellValue("Sheet2", "D11")
assert.NoError(t, err)
_, err = f.GetCellValue("Sheet2", "D12")
assert.NoError(t, err)
// Test SetCellValue function
assert.NoError(t, f.SetCellValue("Sheet2", "F1", " Hello"))
assert.NoError(t, f.SetCellValue("Sheet2", "G1", []byte("World")))
assert.NoError(t, f.SetCellValue("Sheet2", "F2", 42))
assert.NoError(t, f.SetCellValue("Sheet2", "F3", int8(1<<8/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F4", int16(1<<16/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F5", int32(1<<32/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F6", int64(1<<32/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F7", float32(42.65418)))
assert.NoError(t, f.SetCellValue("Sheet2", "F8", -42.65418))
assert.NoError(t, f.SetCellValue("Sheet2", "F9", float32(42)))
assert.NoError(t, f.SetCellValue("Sheet2", "F10", float64(42)))
assert.NoError(t, f.SetCellValue("Sheet2", "F11", uint(1<<32-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F12", uint8(1<<8-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F13", uint16(1<<16-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F14", uint32(1<<32-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F15", uint64(1<<32-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F16", true))
assert.NoError(t, f.SetCellValue("Sheet2", "F17", complex64(5+10i)))
// Test on not exists worksheet
assert.EqualError(t, f.SetCellDefault("SheetN", "A1", ""), "sheet SheetN does not exist")
assert.EqualError(t, f.SetCellFloat("SheetN", "A1", 42.65418, 2, 32), "sheet SheetN does not exist")
assert.EqualError(t, f.SetCellBool("SheetN", "A1", true), "sheet SheetN does not exist")
assert.EqualError(t, f.SetCellFormula("SheetN", "A1", ""), "sheet SheetN does not exist")
assert.EqualError(t, f.SetCellHyperLink("SheetN", "A1", "Sheet1!A40", "Location"), "sheet SheetN does not exist")
// Test boolean write
boolTest := []struct {
value bool
raw bool
expected string
}{
{false, true, "0"},
{true, true, "1"},
{false, false, "FALSE"},
{true, false, "TRUE"},
}
for _, test := range boolTest {
assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value))
val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw})
assert.NoError(t, err)
assert.Equal(t, test.expected, val)
}
assert.NoError(t, f.SetCellValue("Sheet2", "G2", nil))
assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now()))
assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now().UTC()))
assert.EqualError(t, f.SetCellValue("SheetN", "A1", time.Now()), "sheet SheetN does not exist")
// 02:46:40
assert.NoError(t, f.SetCellValue("Sheet2", "G5", time.Duration(1e13)))
// Test completion column
assert.NoError(t, f.SetCellValue("Sheet2", "M2", nil))
// Test read cell value with given cell reference large than exists row
_, err = f.GetCellValue("Sheet2", "E231")
assert.NoError(t, err)
// Test get active worksheet of spreadsheet and get worksheet name of
// spreadsheet by given worksheet index
f.GetSheetName(f.GetActiveSheetIndex())
// Test get worksheet index of spreadsheet by given worksheet name
_, err = f.GetSheetIndex("Sheet1")
assert.NoError(t, err)
// Test get worksheet name of spreadsheet by given invalid worksheet index
f.GetSheetName(4)
// Test get worksheet map of workbook
f.GetSheetMap()
for i := 1; i <= 300; i++ {
assert.NoError(t, f.SetCellStr("Sheet2", "c"+strconv.Itoa(i), strconv.Itoa(i)))
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOpenFile.xlsx")))
assert.EqualError(t, f.SaveAs(filepath.Join("test", strings.Repeat("c", 199), ".xlsx")), ErrMaxFilePathLength.Error())
assert.NoError(t, f.Close())
}
func TestSaveFile(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsb")), ErrWorkbookFileFormat.Error())
for _, ext := range []string{".xlam", ".xlsm", ".xlsx", ".xltm", ".xltx"} {
assert.NoError(t, f.SaveAs(filepath.Join("test", fmt.Sprintf("TestSaveFile%s", ext))))
}
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.Save())
assert.NoError(t, f.Close())
t.Run("for_save_multiple_times", func(t *testing.T) {
{
f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A20", 20))
assert.NoError(t, f.Save())
assert.NoError(t, f.SetCellValue("Sheet1", "A21", 21))
assert.NoError(t, f.Save())
assert.NoError(t, f.Close())
}
{
f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
assert.NoError(t, err)
val, err := f.GetCellValue("Sheet1", "A20")
assert.NoError(t, err)
assert.Equal(t, "20", val)
val, err = f.GetCellValue("Sheet1", "A21")
assert.NoError(t, err)
assert.Equal(t, "21", val)
assert.NoError(t, f.Close())
}
})
}
func TestSaveAsWrongPath(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test write file to not exist directory
assert.Error(t, f.SaveAs(filepath.Join("x", "Book1.xlsx")))
assert.NoError(t, f.Close())
}
func TestCharsetTranscoder(t *testing.T) {
f := NewFile()
f.CharsetTranscoder(charset.NewReaderLabel)
}
func TestOpenReader(t *testing.T) {
_, err := OpenReader(strings.NewReader(""))
assert.EqualError(t, err, zip.ErrFormat.Error())
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password", UnzipXMLSizeLimit: UnzipSizeLimit + 1})
assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
// Prepare unusual workbook, made the specified internal XML parts missing
// or contain unsupported charset
preset := func(filePath string, notExist bool) *bytes.Buffer {
source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)
for _, item := range source.File {
// The following statements can be simplified as zw.Copy(item) in go1.17
if notExist && item.Name == filePath {
continue
}
writer, err := zw.Create(item.Name)
assert.NoError(t, err)
readerCloser, err := item.Open()
assert.NoError(t, err)
_, err = io.Copy(writer, readerCloser)
assert.NoError(t, err)
}
if !notExist {
fi, err := zw.Create(filePath)
assert.NoError(t, err)
_, err = fi.Write(MacintoshCyrillicCharset)
assert.NoError(t, err)
}
assert.NoError(t, zw.Close())
return buf
}
// Test open workbook with unsupported charset internal XML parts
for _, defaultXMLPath := range []string{
defaultXMLPathCalcChain,
defaultXMLPathStyles,
defaultXMLPathWorkbookRels,
} {
_, err = OpenReader(preset(defaultXMLPath, false))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
// Test open workbook without internal XML parts
for _, defaultXMLPath := range []string{
defaultXMLPathCalcChain,
defaultXMLPathStyles,
defaultXMLPathWorkbookRels,
} {
_, err = OpenReader(preset(defaultXMLPath, true))
assert.NoError(t, err)
}
// Test open spreadsheet with unzip size limit
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
assert.EqualError(t, err, newUnzipSizeLimitError(100).Error())
// Test open password protected spreadsheet created by Microsoft Office Excel 2010
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
assert.NoError(t, err)
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "SECRET", val)
assert.NoError(t, f.Close())
// Test open password protected spreadsheet created by LibreOffice 7.0.0.3
f, err = OpenFile(filepath.Join("test", "encryptAES.xlsx"), Options{Password: "password"})
assert.NoError(t, err)
val, err = f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "SECRET", val)
assert.NoError(t, f.Close())
// Test open spreadsheet with invalid options
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{UnzipSizeLimit: 1, UnzipXMLSizeLimit: 2})
assert.EqualError(t, err, ErrOptionsUnzipSizeLimit.Error())
// Test unexpected EOF
var b bytes.Buffer
w := gzip.NewWriter(&b)
defer func() {
assert.NoError(t, w.Close())
}()
assert.NoError(t, w.Flush())
r, _ := gzip.NewReader(&b)
defer func() {
assert.EqualError(t, r.Close(), "unexpected EOF")
}()
_, err = OpenReader(r)
assert.EqualError(t, err, "unexpected EOF")
_, err = OpenReader(bytes.NewReader([]byte{
0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00,
0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00,
0x00, 0x21, 0x06, 0x59, 0xc0, 0x12, 0xf3, 0x19, 0xc7, 0x51, 0xd1, 0xc9, 0x31, 0xcb, 0xcc, 0x8a,
0xe1, 0x44, 0xe1, 0x56, 0x20, 0x24, 0x1f, 0xba, 0x09, 0xda, 0x53, 0xd5, 0xef, 0x50, 0x4b, 0x07,
0x08, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01,
0x02, 0x1f, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00, 0x00,
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00, 0x00, 0x50, 0x4b,
0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x41, 0x00, 0x00, 0x00, 0x5d, 0x00,
0x00, 0x00, 0x00, 0x00,
}))
assert.EqualError(t, err, zip.ErrAlgorithm.Error())
}
func TestBrokenFile(t *testing.T) {
// Test write file with broken file struct
f := File{ZipWriter: func(w io.Writer) ZipWriter { return zip.NewWriter(w) }}
t.Run("SaveWithoutName", func(t *testing.T) {
assert.EqualError(t, f.Save(), "no path defined for file, consider File.WriteTo or File.Write")
})
t.Run("SaveAsEmptyStruct", func(t *testing.T) {
// Test write file with broken file struct with given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx")))
})
t.Run("OpenBadWorkbook", func(t *testing.T) {
// Test set active sheet without BookViews and Sheets maps in xl/workbook.xml
f3, err := OpenFile(filepath.Join("test", "BadWorkbook.xlsx"))
f3.GetActiveSheetIndex()
f3.SetActiveSheet(1)
assert.NoError(t, err)
assert.NoError(t, f3.Close())
})
t.Run("OpenNotExistsFile", func(t *testing.T) {
// Test open a spreadsheet file with given illegal path
_, err := OpenFile(filepath.Join("test", "NotExistsFile.xlsx"))
if assert.Error(t, err) {
assert.True(t, os.IsNotExist(err), "Expected os.IsNotExists(err) == true")
}
})
}
func TestNewFile(t *testing.T) {
// Test create a spreadsheet file
f := NewFile()
_, err := f.NewSheet("Sheet1")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetCellInt("Sheet2", "A23", 56))
assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42"))
f.SetActiveSheet(0)
// Test add picture to sheet with scaling and positioning
assert.NoError(t, f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
&GraphicOptions{ScaleX: 0.5, ScaleY: 0.5, Positioning: "absolute"}))
// Test add picture to worksheet without options
assert.NoError(t, f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewFile.xlsx")))
assert.NoError(t, f.Save())
}
func TestSetCellHyperLink(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test set cell hyperlink in a work sheet already have hyperlinks
assert.NoError(t, f.SetCellHyperLink("Sheet1", "B19", "https://github.com/xuri/excelize", "External"))
// Test add first hyperlink in a work sheet
assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/xuri/excelize", "External"))
// Test add Location hyperlink in a work sheet
assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location"))
// Test add Location hyperlink with display & tooltip in a work sheet
display, tooltip := "Display value", "Hover text"
assert.NoError(t, f.SetCellHyperLink("Sheet2", "D7", "Sheet1!D9", "Location", HyperlinkOpts{
Display: &display,
Tooltip: &tooltip,
}))
cells, err := f.GetHyperLinkCells("Sheet1", "")
assert.NoError(t, err)
assert.Equal(t, []string{"A22", "B19"}, cells)
cells, err = f.GetHyperLinkCells("Sheet2", "")
assert.NoError(t, err)
assert.Equal(t, []string{"C1", "D6", "D7"}, cells)
cells, err = f.GetHyperLinkCells("Sheet2", "External")
assert.NoError(t, err)
assert.Equal(t, []string{"C1"}, cells)
cells, err = f.GetHyperLinkCells("Sheet2", "Location")
assert.NoError(t, err)
assert.Equal(t, []string{"D6", "D7"}, cells)
cells, err = f.GetHyperLinkCells("Sheet2", "None")
assert.NoError(t, err)
assert.Empty(t, cells)
// Test get hyperlink cells on not exists worksheet
_, err = f.GetHyperLinkCells("SheetN", "")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get hyperlink cells with invalid link type
_, err = f.GetHyperLinkCells("Sheet2", "InvalidType")
assert.Equal(t, err, newInvalidLinkTypeError("InvalidType"))
// Test set cell hyperlink with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetCellHyperLink("Sheet:1", "A1", "Sheet1!D60", "Location"))
assert.Equal(t, newInvalidLinkTypeError(""), f.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", ""))
assert.EqualError(t, f.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location"), `invalid cell name ""`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
_, err = f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)}
assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/xuri/excelize", "External"), ErrTotalSheetHyperlinks.Error())
f = NewFile()
_, err = f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
err = f.SetCellHyperLink("Sheet1", "A1", "https://github.com/xuri/excelize", "External")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test update cell hyperlink
f = NewFile()
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com", "External"))
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com/xuri/excelize", "External"))
link, target, err := f.GetCellHyperLink("Sheet1", "A1")
assert.Equal(t, link, true)
assert.Equal(t, "https://github.com/xuri/excelize", target)
assert.NoError(t, err)
// Test remove hyperlink for a cell
f = NewFile()
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location"))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A1:D4"
assert.NoError(t, f.SetCellHyperLink("Sheet1", "B2", "", "None"))
// Test remove hyperlink for a cell with invalid cell reference
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location"))
ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A:A"
assert.Error(t, f.SetCellHyperLink("Sheet1", "B2", "", "None"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
// Test get hyperlink cells on worksheet without hyperlinks
f = NewFile()
cells, err = f.GetHyperLinkCells("Sheet1", "")
assert.NoError(t, err)
assert.Empty(t, cells)
}
func TestGetCellHyperLink(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
_, _, err = f.GetCellHyperLink("Sheet1", "")
assert.EqualError(t, err, `invalid cell name ""`)
link, target, err := f.GetCellHyperLink("Sheet1", "A22")
assert.NoError(t, err)
assert.Equal(t, link, true)
assert.Equal(t, target, "https://github.com/xuri/excelize")
link, target, err = f.GetCellHyperLink("Sheet2", "D6")
assert.NoError(t, err)
assert.Equal(t, link, false)
assert.Equal(t, target, "")
link, target, err = f.GetCellHyperLink("Sheet3", "H3")
assert.EqualError(t, err, "sheet Sheet3 does not exist")
assert.Equal(t, link, false)
assert.Equal(t, target, "")
assert.NoError(t, f.Close())
f = NewFile()
_, err = f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{
Hyperlink: []xlsxHyperlink{{Ref: "A1"}},
}
link, target, err = f.GetCellHyperLink("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, link, true)
assert.Equal(t, target, "")
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: []xlsxHyperlink{{Ref: "A:A"}}}
link, target, err = f.GetCellHyperLink("Sheet1", "A1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.Equal(t, link, false)
assert.Equal(t, target, "")
// Test get cell hyperlink with invalid sheet name
_, _, err = f.GetCellHyperLink("Sheet:1", "A1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
}
func TestSetSheetBackground(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")))
assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackground.xlsx")))
assert.NoError(t, f.Close())
}
func TestSetSheetBackgroundErrors(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "not_exists", "not_exists.png"))
if assert.Error(t, err) {
assert.True(t, os.IsNotExist(err), "Expected os.IsNotExists(err) == true")
}
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx"))
assert.EqualError(t, err, ErrImgExt.Error())
// Test set sheet background on not exist worksheet
err = f.SetSheetBackground("SheetN", filepath.Join("test", "images", "background.jpg"))
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test set sheet background with invalid sheet name
assert.EqualError(t, f.SetSheetBackground("Sheet:1", filepath.Join("test", "images", "background.jpg")), ErrSheetNameInvalid.Error())
assert.NoError(t, f.Close())
// Test set sheet background with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8")
}
// TestWriteArrayFormula tests the extended options of SetCellFormula by writing
// an array function to a workbook. In the resulting file, the lines 2 and 3 as
// well as 4 and 5 should have matching contents
func TestWriteArrayFormula(t *testing.T) {
cell := func(col, row int) string {
c, err := CoordinatesToCellName(col, row)
if err != nil {
t.Fatal(err)
}
return c
}
f := NewFile()
sample := []string{"Sample 1", "Sample 2", "Sample 3"}
values := []int{1855, 1709, 1462, 1115, 1524, 625, 773, 126, 1027, 1696, 1078, 1917, 1109, 1753, 1884, 659, 994, 1911, 1925, 899, 196, 244, 1488, 1056, 1986, 66, 784, 725, 767, 1722, 1541, 1026, 1455, 264, 1538, 877, 1581, 1098, 383, 762, 237, 493, 29, 1923, 474, 430, 585, 688, 308, 200, 1259, 622, 798, 1048, 996, 601, 582, 332, 377, 805, 250, 1860, 1360, 840, 911, 1346, 1651, 1651, 665, 584, 1057, 1145, 925, 1752, 202, 149, 1917, 1398, 1894, 818, 714, 624, 1085, 1566, 635, 78, 313, 1686, 1820, 494, 614, 1913, 271, 1016, 338, 1301, 489, 1733, 1483, 1141}
assoc := []int{2, 0, 0, 0, 0, 1, 1, 0, 0, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 1, 0, 2, 0, 2, 1, 2, 2, 2, 1, 0, 1, 0, 1, 1, 2, 0, 2, 1, 0, 2, 1, 0, 1, 0, 0, 2, 0, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 1, 1, 0, 1, 0, 2, 0, 0, 1, 2, 1, 0, 1, 0, 0, 2, 1, 1, 2, 0, 2, 1, 0, 2, 2, 2, 1, 0, 0, 1, 1, 1, 2, 0, 2, 0, 1, 1}
if len(values) != len(assoc) {
t.Fatal("values and assoc must be of same length")
}
// Average calculates the average of the n-th sample (0 <= n < len(sample)).
average := func(n int) int {
sum := 0
count := 0
for i := 0; i != len(values); i++ {
if assoc[i] == n {
sum += values[i]
count++
}
}
return int(math.Round(float64(sum) / float64(count)))
}
// Stdev calculates the standard deviation of the n-th sample (0 <= n < len(sample)).
stdev := func(n int) int {
avg := average(n)
sum := 0
count := 0
for i := 0; i != len(values); i++ {
if assoc[i] == n {
sum += (values[i] - avg) * (values[i] - avg)
count++
}
}
return int(math.Round(math.Sqrt(float64(sum) / float64(count))))
}
// Line 2 contains the results of AVERAGEIF
assert.NoError(t, f.SetCellStr("Sheet1", "A2", "Average"))
// Line 3 contains the average that was calculated in Go
assert.NoError(t, f.SetCellStr("Sheet1", "A3", "Average (calculated)"))
// Line 4 contains the results of the array function that calculates the standard deviation
assert.NoError(t, f.SetCellStr("Sheet1", "A4", "Std. deviation"))
// Line 5 contains the standard deviations calculated in Go
assert.NoError(t, f.SetCellStr("Sheet1", "A5", "Std. deviation (calculated)"))
assert.NoError(t, f.SetCellStr("Sheet1", "B1", sample[0]))
assert.NoError(t, f.SetCellStr("Sheet1", "C1", sample[1]))
assert.NoError(t, f.SetCellStr("Sheet1", "D1", sample[2]))
firstResLine := 8
assert.NoError(t, f.SetCellStr("Sheet1", cell(1, firstResLine-1), "Result Values"))
assert.NoError(t, f.SetCellStr("Sheet1", cell(2, firstResLine-1), "Sample"))
for i := 0; i != len(values); i++ {
valCell := cell(1, i+firstResLine)
assocCell := cell(2, i+firstResLine)
assert.NoError(t, f.SetCellInt("Sheet1", valCell, int64(values[i])))
assert.NoError(t, f.SetCellStr("Sheet1", assocCell, sample[assoc[i]]))
}
valRange := fmt.Sprintf("$A$%d:$A$%d", firstResLine, len(values)+firstResLine-1)
assocRange := fmt.Sprintf("$B$%d:$B$%d", firstResLine, len(values)+firstResLine-1)
for i := 0; i != len(sample); i++ {
nameCell := cell(i+2, 1)
avgCell := cell(i+2, 2)
calcAvgCell := cell(i+2, 3)
stdevCell := cell(i+2, 4)
calcStdevCell := cell(i+2, 5)
assert.NoError(t, f.SetCellInt("Sheet1", calcAvgCell, int64(average(i))))
assert.NoError(t, f.SetCellInt("Sheet1", calcStdevCell, int64(stdev(i))))
// Average can be done with AVERAGEIF
assert.NoError(t, f.SetCellFormula("Sheet1", avgCell, fmt.Sprintf("ROUND(AVERAGEIF(%s,%s,%s),0)", assocRange, nameCell, valRange)))
ref := stdevCell + ":" + stdevCell
arr := STCellFormulaTypeArray
// Use an array formula for standard deviation
assert.NoError(t, f.SetCellFormula("Sheet1", stdevCell, fmt.Sprintf("ROUND(STDEVP(IF(%s=%s,%s)),0)", assocRange, nameCell, valRange),
FormulaOpts{}, FormulaOpts{Type: &arr}, FormulaOpts{Ref: &ref}))
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestWriteArrayFormula.xlsx")))
}
func TestSetCellStyleAlignment(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
var style int
style, err = f.NewStyle(&Style{Alignment: &Alignment{Horizontal: "center", Indent: 1, JustifyLastLine: true, ReadingOrder: 0, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 45, Vertical: "top", WrapText: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style))
// Test set cell style with given illegal rows number
assert.EqualError(t, f.SetCellStyle("Sheet1", "A", "A22", style), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.SetCellStyle("Sheet1", "A22", "A", style), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set cell style with invalid sheet name
assert.EqualError(t, f.SetCellStyle("Sheet:1", "A1", "A2", style), ErrSheetNameInvalid.Error())
// Test get cell style with given illegal rows number
index, err := f.GetCellStyle("Sheet1", "A")
assert.Equal(t, 0, index)
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test get cell style with invalid sheet name
_, err = f.GetCellStyle("Sheet:1", "A1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleAlignment.xlsx")))
}
func TestSetCellStyleBorder(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
var style int
// Test set border on overlapping range with vertical variants shading styles gradient fill
style, err = f.NewStyle(&Style{
Border: []Border{
{Type: "left", Color: "0000FF", Style: 3},
{Type: "top", Color: "00FF00", Style: 4},
{Type: "bottom", Color: "FFFF00", Style: 5},
{Type: "right", Color: "FF0000", Style: 6},
{Type: "diagonalDown", Color: "A020F0", Style: 7},
{Type: "diagonalUp", Color: "A020F0", Style: 8},
},
})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "J21", "L25", style))
style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style))
style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 4}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style))
// Test set border and solid style pattern fill for a single cell
style, err = f.NewStyle(&Style{
Border: []Border{
{
Type: "left",
Color: "0000FF",
Style: 8,
},
{
Type: "top",
Color: "00FF00",
Style: 9,
},
{
Type: "bottom",
Color: "FFFF00",
Style: 10,
},
{
Type: "right",
Color: "FF0000",
Style: 11,
},
{
Type: "diagonalDown",
Color: "A020F0",
Style: 12,
},
{
Type: "diagonalUp",
Color: "A020F0",
Style: 13,
},
},
Fill: Fill{
Type: "pattern",
Color: []string{"E0EBF5"},
Pattern: 1,
},
})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleBorder.xlsx")))
}
func TestSetCellStyleBorderErrors(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
// Set border with invalid style index number
_, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: -1}, {Type: "top", Color: "00FF00", Style: 14}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "diagonalDown", Color: "A020F0", Style: 9}, {Type: "diagonalUp", Color: "A020F0", Style: 8}}})
assert.NoError(t, err)
}
func TestSetCellStyleNumberFormat(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
// Test only set fill and number format for a cell
col := []string{"L", "M", "N", "O", "P"}
idxTbl := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
expected := [][]string{
{"37947.75", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947 3/4", "37947 3/4 ", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", " 37,948 ", " $37,948 ", " 37,947.75 ", " $37,947.75 ", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
{"-37947.75", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947 3/4", "-37947 3/4 ", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", " (37,948)", " $(37,948)", " (37,947.75)", " $(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0 ", "0 ", "01-00-00", "0-Jan-00", "0-Jan", "Jan-00", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "1/0/00 00:10", "0 ", "0 ", "0.01 ", "0.01 ", " 0 ", " $0 ", " 0.01 ", " $0.01 ", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2 1/9", "2 1/10", "01-02-00", "2-Jan-00", "2-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/2/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", " 2 ", " $2 ", " 2.10 ", " $2.10 ", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", " String ", " String ", " String ", " String ", "String", "String", "String", "String", "String"},
}
for c, v := range value {
for r, idx := range idxTbl {
cell := col[c] + strconv.Itoa(r+1)
var val float64
val, err = strconv.ParseFloat(v, 64)
if err != nil {
assert.NoError(t, f.SetCellValue("Sheet2", cell, v))
} else {
assert.NoError(t, f.SetCellValue("Sheet2", cell, val))
}
style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 5}, NumFmt: idx})
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", cell, cell, style))
cellValue, err := f.GetCellValue("Sheet2", cell)
assert.Equal(t, expected[c][r], cellValue, fmt.Sprintf("Sheet2!%s value: %s, number format: %s c: %d r: %d", cell, value[c], builtInNumFmt[idx], c, r))
assert.NoError(t, err)
}
}
var style int
style, err = f.NewStyle(&Style{NumFmt: -1})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx")))
// Test get cell value with built-in number format code 22 with custom short date pattern
f = NewFile(Options{ShortDatePattern: "yyyy-m-dd"})
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 45074.625694444447))
style, err = f.NewStyle(&Style{NumFmt: 22})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
cellValue, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "2023-5-28 15:01", cellValue)
}
func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3))
var style int
style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: intPtr(-1)})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: intPtr(31), NegRed: true})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook3.xlsx")))
})
t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4()
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
_, err = f.NewStyle(&Style{NumFmt: 26})
assert.NoError(t, err)
style, err := f.NewStyle(&Style{NumFmt: 27})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(&Style{NumFmt: 31})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
style, err = f.NewStyle(&Style{NumFmt: 71})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx")))
})
}
func TestSetCellStyleLangNumberFormat(t *testing.T) {
rawCellValues := make([][]string, 42)
for i := 0; i < 42; i++ {
rawCellValues[i] = []string{"45162"}
}
for lang, expected := range map[CultureName][][]string{
CultureNameUnknown: rawCellValues,
CultureNameEnUS: {{"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"45162"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0時00分"}, {"0時00分00秒"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"08-24-56"}, {"4356년 08월 24일"}, {"0시 00분"}, {"0시 00분 00초"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0时00分"}, {"0时00分00秒"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"00時00分"}, {"00時00分00秒"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
} {
f, err := prepareTestBook5(Options{CultureInfo: lang})
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, rows)
assert.NoError(t, f.Close())
}
// Test apply language number format code with date and time pattern
for lang, expected := range map[CultureName][][]string{
CultureNameEnUS: {{"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"45162"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"4356-8-24"}, {"4356년 08월 24일"}, {"00:00:00"}, {"00:00:00"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
} {
f, err := prepareTestBook5(Options{CultureInfo: lang, ShortDatePattern: "yyyy-M-d", LongTimePattern: "hh:mm:ss"})
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, rows)
assert.NoError(t, f.Close())
}
// Test open workbook with invalid date and time pattern options
_, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongDatePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongTimePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{ShortDatePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
}
func TestSetCellStyleCustomNumberFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
customNumFmt := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"
style, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt, Font: &Font{Color: "9A0511"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
customNumFmt = "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@"
_, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx")))
}
func TestSetCellStyleFill(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
var style int
// Test set fill for cell with invalid parameter
style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 6}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{}, Shading: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx")))
}
func TestSetCellStyleFont(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
var style int
style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "777777", Underline: "single"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style))
style, err = f.NewStyle(&Style{Font: &Font{Italic: true, Underline: "double"}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style))
style, err = f.NewStyle(&Style{Font: &Font{Bold: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style))
style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Family: "", Size: 0, Color: "", Underline: ""}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style))
style, err = f.NewStyle(&Style{Font: &Font{Color: "777777", Strike: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFont.xlsx")))
}
func TestSetCellStyleProtection(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
var style int
style, err = f.NewStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style))
err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx"))
assert.NoError(t, err)
}
func TestSetDeleteSheet(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3()
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet3"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx")))
})
t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4()
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet1"))
assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A1", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx")))
})
}
func TestSheetVisibility(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
assert.NoError(t, f.SetSheetVisible("Sheet2", false))
assert.NoError(t, f.SetSheetVisible("Sheet2", false, true))
assert.NoError(t, f.SetSheetVisible("Sheet1", false))
assert.NoError(t, f.SetSheetVisible("Sheet1", true))
visible, err := f.GetSheetVisible("Sheet1")
assert.Equal(t, true, visible)
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSheetVisibility.xlsx")))
}
func TestCopySheet(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
idx, err := f.NewSheet("CopySheet")
assert.NoError(t, err)
assert.NoError(t, f.CopySheet(0, idx))
assert.NoError(t, f.SetCellValue("CopySheet", "F1", "Hello"))
val, err := f.GetCellValue("Sheet1", "F1")
assert.NoError(t, err)
assert.NotEqual(t, "Hello", val)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCopySheet.xlsx")))
}
func TestCopySheetError(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
assert.EqualError(t, f.copySheet(-1, -2), ErrSheetNameBlank.Error())
assert.EqualError(t, f.CopySheet(-1, -2), ErrSheetIdx.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCopySheetError.xlsx")))
}
func TestGetSheetComments(t *testing.T) {
f := NewFile()
assert.Empty(t, f.getSheetComments("sheet0"))
}
func TestGetActiveSheetIndex(t *testing.T) {
f := NewFile()
f.WorkBook.BookViews = nil
assert.Equal(t, 0, f.GetActiveSheetIndex())
}
func TestRelsWriter(t *testing.T) {
f := NewFile()
f.Relationships.Store("xl/worksheets/sheet/rels/sheet1.xml.rel", &xlsxRelationships{})
f.relsWriter()
}
func TestConditionalFormat(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
assert.NoError(t, fillCells(f, sheet1, 10, 15))
var format1, format2, format3, format4 int
var err error
// Rose format for bad conditional
format1, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
// Light yellow format for neutral conditional
format2, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEEAA0"}, Pattern: 1}})
assert.NoError(t, err)
// Light green format for good conditional
format3, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "09600B"}, Fill: Fill{Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1}})
assert.NoError(t, err)
// conditional style with align and left border
format4, err = f.NewConditionalStyle(&Style{Alignment: &Alignment{WrapText: true}, Border: []Border{{Type: "left", Color: "000000", Style: 1}}})
assert.NoError(t, err)
// Color scales: 2 color
assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10",
[]ConditionalFormatOptions{
{
Type: "2_color_scale",
Criteria: "=",
MinType: "min",
MaxType: "max",
MinColor: "#F8696B",
MaxColor: "#63BE7B",
},
},
))
// Color scales: 3 color
assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10",
[]ConditionalFormatOptions{
{
Type: "3_color_scale",
Criteria: "=",
MinType: "min",
MidType: "percentile",
MaxType: "max",
MinColor: "#F8696B",
MidColor: "#FFEB84",
MaxColor: "#63BE7B",
},
},
))
// Highlight cells rules: between...
assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: "between",
Format: &format1,
MinValue: "6",
MaxValue: "8",
},
},
))
// Highlight cells rules: Greater Than...
assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: ">",
Format: &format3,
Value: "6",
},
},
))
// Highlight cells rules: Equal To...
assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10",
[]ConditionalFormatOptions{
{
Type: "top",
Criteria: "=",
Format: &format3,
},
},
))
// Highlight cells rules: Not Equal To...
assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10",
[]ConditionalFormatOptions{
{
Type: "unique",
Criteria: "=",
Format: &format2,
},
},
))
// Highlight cells rules: Duplicate Values...
assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10",
[]ConditionalFormatOptions{
{
Type: "duplicate",
Criteria: "=",
Format: &format2,
},
},
))
// Top/Bottom rules: Top 10%.
assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10",
[]ConditionalFormatOptions{
{
Type: "top",
Criteria: "=",
Format: &format1,
Value: "6",
Percent: true,
},
},
))
// Top/Bottom rules: Above Average...
assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10",
[]ConditionalFormatOptions{
{
Type: "average",
Criteria: "=",
Format: &format3,
AboveAverage: true,
},
},
))
// Top/Bottom rules: Below Average...
assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10",
[]ConditionalFormatOptions{
{
Type: "average",
Criteria: "=",
Format: &format1,
AboveAverage: false,
},
},
))
// Data Bars: Gradient Fill
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "data_bar",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Use a formula to determine which cells to format
assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10",
[]ConditionalFormatOptions{
{
Type: "formula",
Criteria: "L2<3",
Format: &format1,
},
},
))
// Alignment/Border cells rules
assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10",
[]ConditionalFormatOptions{
{
Type: "cell",
Criteria: ">",
Format: &format4,
Value: "0",
},
},
))
// Test set conditional format with invalid cell reference
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.SetConditionalFormat("Sheet1", "A1:-", nil))
// Test set conditional format on not exists worksheet
assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist")
// Test set conditional format with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetConditionalFormat("Sheet:1", "L1:L10", nil))
err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
assert.NoError(t, err)
// Set conditional format with illegal valid type
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "",
Criteria: "=",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Set conditional format with illegal criteria type
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "data_bar",
Criteria: "",
MinType: "min",
MaxType: "max",
BarColor: "#638EC6",
},
},
))
// Test create conditional format with invalid custom number format
var exp string
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &exp})
assert.Equal(t, ErrCustomNumFmt, err)
// Set conditional format with file without dxfs element should not return error
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.Close())
}
func TestSharedStrings(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "A", rows[0][0])
rows, err = f.GetRows("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "Test Weight (Kgs)", rows[0][0])
assert.NoError(t, f.Close())
}
func TestSetSheetCol(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetSheetCol("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()}))
assert.EqualError(t, f.SetSheetCol("Sheet1", "", &[]interface{}{"cell", nil, 2}),
newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
// Test set worksheet column values with invalid sheet name
assert.EqualError(t, f.SetSheetCol("Sheet:1", "A1", &[]interface{}{nil}), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.SetSheetCol("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error())
assert.EqualError(t, f.SetSheetCol("Sheet1", "B27", &f), ErrParameterInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetCol.xlsx")))
assert.NoError(t, f.Close())
}
func TestSetSheetRow(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()}))
assert.EqualError(t, f.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}),
newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
// Test set worksheet row with invalid sheet name
assert.EqualError(t, f.SetSheetRow("Sheet:1", "A1", &[]interface{}{1}), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error())
assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", &f), ErrParameterInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx")))
assert.NoError(t, f.Close())
}
func TestHSL(t *testing.T) {
var hsl HSL
r, g, b, a := hsl.RGBA()
assert.Equal(t, uint32(0), r)
assert.Equal(t, uint32(0), g)
assert.Equal(t, uint32(0), b)
assert.Equal(t, uint32(0xffff), a)
assert.Equal(t, HSL{0, 0, 0}, hslModel(hsl))
assert.Equal(t, HSL{0, 0, 0}, hslModel(color.Gray16{Y: uint16(1)}))
R, G, B := HSLToRGB(0, 1, 0.4)
assert.Equal(t, uint8(204), R)
assert.Equal(t, uint8(0), G)
assert.Equal(t, uint8(0), B)
R, G, B = HSLToRGB(0, 1, 0.6)
assert.Equal(t, uint8(255), R)
assert.Equal(t, uint8(51), G)
assert.Equal(t, uint8(51), B)
assert.Equal(t, 0.0, hueToRGB(0, 0, -1))
assert.Equal(t, 0.0, hueToRGB(0, 0, 2))
assert.Equal(t, 0.0, hueToRGB(0, 0, 1.0/7))
assert.Equal(t, 0.0, hueToRGB(0, 0, 0.4))
assert.Equal(t, 0.0, hueToRGB(0, 0, 2.0/4))
h, s, l := RGBToHSL(255, 255, 0)
assert.Equal(t, 0.16666666666666666, h)
assert.Equal(t, 1.0, s)
assert.Equal(t, 0.5, l)
h, s, l = RGBToHSL(0, 255, 255)
assert.Equal(t, 0.5, h)
assert.Equal(t, 1.0, s)
assert.Equal(t, 0.5, l)
h, s, l = RGBToHSL(250, 100, 50)
assert.Equal(t, 0.041666666666666664, h)
assert.Equal(t, 0.9523809523809524, s)
assert.Equal(t, 0.5882352941176471, l)
h, s, l = RGBToHSL(50, 100, 250)
assert.Equal(t, 0.625, h)
assert.Equal(t, 0.9523809523809524, s)
assert.Equal(t, 0.5882352941176471, l)
h, s, l = RGBToHSL(250, 50, 100)
assert.Equal(t, 0.9583333333333334, h)
assert.Equal(t, 0.9523809523809524, s)
assert.Equal(t, 0.5882352941176471, l)
}
func TestProtectSheet(t *testing.T) {
f := NewFile()
sheetName := f.GetSheetName(0)
assert.EqualError(t, f.ProtectSheet(sheetName, nil), ErrParameterInvalid.Error())
// Test protect worksheet with XOR hash algorithm
assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{
Password: "password",
EditScenarios: false,
}))
opts, err := f.GetSheetProtection(sheetName)
assert.NoError(t, err)
assert.Equal(t, false, opts.EditScenarios)
ws, err := f.workSheetReader(sheetName)
assert.NoError(t, err)
assert.Equal(t, "83AF", ws.SheetProtection.Password)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectSheet.xlsx")))
// Test protect worksheet with SHA-512 hash algorithm
assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{
AlgorithmName: "SHA-512",
Password: "password",
}))
ws, err = f.workSheetReader(sheetName)
assert.NoError(t, err)
assert.Len(t, ws.SheetProtection.SaltValue, 24)
assert.Len(t, ws.SheetProtection.HashValue, 88)
assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount)
// Test remove sheet protection with an incorrect password
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
// Test remove sheet protection with invalid sheet name
assert.EqualError(t, f.UnprotectSheet("Sheet:1", "wrongPassword"), ErrSheetNameInvalid.Error())
// Test remove sheet protection with password verification
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
// Test protect worksheet with empty password
assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{}))
assert.Empty(t, ws.SheetProtection.Password)
// Test protect worksheet with password exceeds the limit length
assert.EqualError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{
AlgorithmName: "MD4",
Password: strings.Repeat("s", MaxFieldLength+1),
}), ErrPasswordLengthInvalid.Error())
// Test protect worksheet with unsupported hash algorithm
assert.EqualError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{
AlgorithmName: "RIPEMD-160",
Password: "password",
}), ErrUnsupportedHashAlgorithm.Error())
// Test protect not exists worksheet
assert.EqualError(t, f.ProtectSheet("SheetN", nil), "sheet SheetN does not exist")
// Test protect sheet with invalid sheet name
assert.EqualError(t, f.ProtectSheet("Sheet:1", nil), ErrSheetNameInvalid.Error())
// Test get sheet protection on not exists worksheet
_, err = f.GetSheetProtection("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
}
func TestUnprotectSheet(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test remove protection on not exists worksheet
assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist")
assert.NoError(t, f.UnprotectSheet("Sheet1"))
assert.EqualError(t, f.UnprotectSheet("Sheet1", "password"), ErrUnprotectSheet.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectSheet.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
sheetName := f.GetSheetName(0)
assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{Password: "password"}))
// Test remove sheet protection with an incorrect password
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
// Test remove sheet protection with password verification
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
// Test with invalid salt value
assert.NoError(t, f.ProtectSheet(sheetName, &SheetProtectionOptions{
AlgorithmName: "SHA-512",
Password: "password",
}))
ws, err := f.workSheetReader(sheetName)
assert.NoError(t, err)
ws.SheetProtection.SaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), "illegal base64 data at input byte 8")
}
func TestProtectWorkbook(t *testing.T) {
f := NewFile()
assert.NoError(t, f.ProtectWorkbook(nil))
// Test protect workbook with default hash algorithm
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
Password: "password",
LockStructure: true,
}))
wb, err := f.workbookReader()
assert.NoError(t, err)
assert.Equal(t, "SHA-512", wb.WorkbookProtection.WorkbookAlgorithmName)
assert.Len(t, wb.WorkbookProtection.WorkbookSaltValue, 24)
assert.Len(t, wb.WorkbookProtection.WorkbookHashValue, 88)
assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount)
// Test protect workbook with password exceeds the limit length
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "MD4",
Password: strings.Repeat("s", MaxFieldLength+1),
}), ErrPasswordLengthInvalid.Error())
// Test protect workbook with unsupported hash algorithm
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "RIPEMD-160",
Password: "password",
}), ErrUnsupportedHashAlgorithm.Error())
// Test protect workbook with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.ProtectWorkbook(nil), "XML syntax error on line 1: invalid UTF-8")
}
func TestUnprotectWorkbook(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.UnprotectWorkbook())
assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectWorkbook.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{Password: "password"}))
// Test remove workbook protection with an incorrect password
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), ErrUnprotectWorkbookPassword.Error())
// Test remove workbook protection with password verification
assert.NoError(t, f.UnprotectWorkbook("password"))
// Test with invalid salt value
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "SHA-512",
Password: "password",
}))
wb, err := f.workbookReader()
assert.NoError(t, err)
wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8")
// Test remove workbook protection with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UnprotectWorkbook(), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDefaultTimeStyle(t *testing.T) {
f := NewFile()
// Test set default time style on not exists worksheet.
assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN does not exist")
// Test set default time style on invalid cell
assert.EqualError(t, f.setDefaultTimeStyle("Sheet1", "", 42), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
}
func TestAddVBAProject(t *testing.T) {
f := NewFile()
file, err := os.ReadFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")}))
assert.EqualError(t, f.AddVBAProject(file), ErrAddVBAProject.Error())
file, err = os.ReadFile(filepath.Join("test", "vbaProject.bin"))
assert.NoError(t, err)
assert.NoError(t, f.AddVBAProject(file))
// Test add VBA project twice
assert.NoError(t, f.AddVBAProject(file))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm")))
// Test add VBA with unsupported charset workbook relationships
f.Relationships.Delete(defaultXMLPathWorkbookRels)
f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddVBAProject(file), "XML syntax error on line 1: invalid UTF-8")
}
func TestContentTypesReader(t *testing.T) {
// Test unsupported charset
f := NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
_, err := f.contentTypesReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestWorkbookReader(t *testing.T) {
// Test unsupported charset
f := NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err := f.workbookReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestWorkSheetReader(t *testing.T) {
// Test unsupported charset
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.workSheetReader("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
// Test on no checked worksheet
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``))
f.checked = sync.Map{}
_, err = f.workSheetReader("Sheet1")
assert.NoError(t, err)
}
func TestRelsReader(t *testing.T) {
// Test unsupported charset
f := NewFile()
rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil)
f.Pkg.Store(rels, MacintoshCyrillicCharset)
_, err := f.relsReader(rels)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteSheetFromWorkbookRels(t *testing.T) {
f := NewFile()
rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil)
assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "")
}
func TestUpdateLinkedValue(t *testing.T) {
f := NewFile()
// Test update lined value with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
}
func TestAttrValToInt(t *testing.T) {
_, err := attrValToInt("r", []xml.Attr{
{Name: xml.Name{Local: "r"}, Value: "s"},
})
assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`)
}
func prepareTestBook1() (*File, error) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if err != nil {
return nil, err
}
if err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"),
&GraphicOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}); err != nil {
return nil, err
}
// Test add picture to worksheet with offset, external hyperlink and positioning
if err := f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"),
&GraphicOptions{
OffsetX: 10,
OffsetY: 10,
Hyperlink: "https://github.com/xuri/excelize",
HyperlinkType: "External",
Positioning: "oneCell",
},
); err != nil {
return nil, err
}
file, err := os.ReadFile(filepath.Join("test", "images", "excel.jpg"))
if err != nil {
return nil, err
}
err = f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".jpg", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}})
if err != nil {
return nil, err
}
return f, nil
}
func prepareTestBook3() (*File, error) {
f := NewFile()
if _, err := f.NewSheet("Sheet2"); err != nil {
return nil, err
}
if _, err := f.NewSheet("Sheet3"); err != nil {
return nil, err
}
if err := f.SetCellInt("Sheet2", "A23", 56); err != nil {
return nil, err
}
if err := f.SetCellStr("Sheet1", "B20", "42"); err != nil {
return nil, err
}
f.SetActiveSheet(0)
if err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
&GraphicOptions{ScaleX: 0.5, ScaleY: 0.5, Positioning: "absolute"}); err != nil {
return nil, err
}
if err := f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil); err != nil {
return nil, err
}
return f, nil
}
func prepareTestBook4() (*File, error) {
f := NewFile()
if err := f.SetColWidth("Sheet1", "B", "A", 12); err != nil {
return f, err
}
if err := f.SetColWidth("Sheet1", "A", "B", 12); err != nil {
return f, err
}
if _, err := f.GetColWidth("Sheet1", "A"); err != nil {
return f, err
}
if _, err := f.GetColWidth("Sheet1", "C"); err != nil {
return f, err
}
return f, nil
}
func prepareTestBook5(opts Options) (*File, error) {
f := NewFile(opts)
var rowNum int
for _, idxRange := range [][]int{{27, 36}, {50, 81}} {
for numFmtIdx := idxRange[0]; numFmtIdx <= idxRange[1]; numFmtIdx++ {
rowNum++
styleID, err := f.NewStyle(&Style{NumFmt: numFmtIdx})
if err != nil {
return f, err
}
cell, err := CoordinatesToCellName(1, rowNum)
if err != nil {
return f, err
}
if err := f.SetCellValue("Sheet1", cell, 45162); err != nil {
return f, err
}
if err := f.SetCellStyle("Sheet1", cell, cell, styleID); err != nil {
return f, err
}
}
}
return f, nil
}
func fillCells(f *File, sheet string, colCount, rowCount int) error {
for col := 1; col <= colCount; col++ {
for row := 1; row <= rowCount; row++ {
cell, _ := CoordinatesToCellName(col, row)
if err := f.SetCellStr(sheet, cell, cell); err != nil {
return err
}
}
}
return nil
}
func BenchmarkOpenFile(b *testing.B) {
for i := 0; i < b.N; i++ {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if err != nil {
b.Error(err)
}
if err := f.Close(); err != nil {
b.Error(err)
}
}
}
================================================
FILE: file.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/binary"
"encoding/xml"
"io"
"math"
"os"
"path/filepath"
"sort"
"strings"
"sync"
)
// NewFile provides a function to create new file by default template.
// For example:
//
// f := NewFile()
func NewFile(opts ...Options) *File {
f := newFile()
f.Pkg.Store(defaultXMLPathRels, []byte(xml.Header+templateRels))
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore))
f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels))
f.Pkg.Store(defaultXMLPathTheme, []byte(xml.Header+templateTheme))
f.Pkg.Store(defaultXMLPathSheet, []byte(xml.Header+templateSheet))
f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles))
f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook))
f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes))
f.SheetCount = 1
f.CalcChain, _ = f.calcChainReader()
f.ContentTypes, _ = f.contentTypesReader()
f.Styles, _ = f.stylesReader()
f.WorkBook, _ = f.workbookReader()
f.Relationships = sync.Map{}
rels, _ := f.relsReader(defaultXMLPathWorkbookRels)
f.Relationships.Store(defaultXMLPathWorkbookRels, rels)
f.sheetMap["Sheet1"] = defaultXMLPathSheet
ws, _ := f.workSheetReader("Sheet1")
f.Sheet.Store(defaultXMLPathSheet, ws)
f.Theme, _ = f.themeReader()
f.options = f.getOptions(opts...)
return f
}
// Save provides a function to override the spreadsheet with origin path.
func (f *File) Save(opts ...Options) error {
if f.Path == "" {
return ErrSave
}
for i := range opts {
f.options = &opts[i]
}
return f.SaveAs(f.Path, *f.options)
}
// SaveAs provides a function to create or update to a spreadsheet at the
// provided path.
func (f *File) SaveAs(name string, opts ...Options) error {
if countUTF16String(name) > MaxFilePathLength {
return ErrMaxFilePathLength
}
f.Path = name
if _, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]; !ok {
return ErrWorkbookFileFormat
}
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
if err != nil {
return err
}
defer func() {
_ = file.Close()
}()
return f.Write(file, opts...)
}
// Close closes and cleanup the open temporary file for the spreadsheet.
func (f *File) Close() error {
var firstErr error
if f.sharedStringTemp != nil {
firstErr = f.sharedStringTemp.Close()
f.sharedStringTemp = nil
}
for _, stream := range f.streams {
_ = stream.rawData.Close()
}
f.streams = nil
f.tempFiles.Range(func(k, v interface{}) bool {
if err := os.Remove(v.(string)); err != nil && firstErr == nil {
firstErr = err
}
return true
})
f.tempFiles.Clear()
return firstErr
}
// Write provides a function to write to an io.Writer.
func (f *File) Write(w io.Writer, opts ...Options) error {
_, err := f.WriteTo(w, opts...)
return err
}
// WriteTo implements io.WriterTo to write the file.
func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
for i := range opts {
f.options = &opts[i]
}
if len(f.Path) != 0 {
contentType, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]
if !ok {
return 0, ErrWorkbookFileFormat
}
if err := f.setContentTypePartProjectExtensions(contentType); err != nil {
return 0, err
}
}
buf, err := f.WriteToBuffer()
if err != nil {
return 0, err
}
return buf.WriteTo(w)
}
// WriteToBuffer provides a function to get bytes.Buffer from the saved file,
// and it allocates space in memory. Be careful when the file size is large.
func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
zw := f.ZipWriter(buf)
if err := f.writeToZip(zw); err != nil {
_ = zw.Close()
return buf, err
}
if err := zw.Close(); err != nil {
return buf, err
}
err := f.writeZip64LFH(buf)
if f.options != nil && f.options.Password != "" {
b, err := Encrypt(buf.Bytes(), f.options)
if err != nil {
return buf, err
}
buf.Reset()
buf.Write(b)
}
return buf, err
}
// writeToZip provides a function to write to ZipWriter.
func (f *File) writeToZip(zw ZipWriter) error {
f.calcChainWriter()
f.commentsWriter()
f.contentTypesWriter()
f.drawingsWriter()
f.volatileDepsWriter()
f.vmlDrawingWriter()
f.workBookWriter()
f.workSheetWriter()
f.relsWriter()
_ = f.sharedStringsLoader()
f.sharedStringsWriter()
f.styleSheetWriter()
f.themeWriter()
for path, stream := range f.streams {
fi, err := zw.Create(path)
if err != nil {
return err
}
var from io.Reader
if from, err = stream.rawData.Reader(); err != nil {
_ = stream.rawData.Close()
return err
}
written, err := io.Copy(fi, from)
if err != nil {
return err
}
if written > math.MaxUint32 {
f.zip64Entries = append(f.zip64Entries, path)
}
}
var (
n int
err error
files, tempFiles []string
)
f.Pkg.Range(func(path, content interface{}) bool {
if _, ok := f.streams[path.(string)]; ok {
return true
}
files = append(files, path.(string))
return true
})
sort.Sort(sort.Reverse(sort.StringSlice(files)))
for _, path := range files {
var fi io.Writer
if fi, err = zw.Create(path); err != nil {
break
}
content, _ := f.Pkg.Load(path)
if n, err = fi.Write(content.([]byte)); int64(n) > math.MaxUint32 {
f.zip64Entries = append(f.zip64Entries, path)
}
}
f.tempFiles.Range(func(path, content interface{}) bool {
if _, ok := f.Pkg.Load(path); ok {
return true
}
tempFiles = append(tempFiles, path.(string))
return true
})
sort.Sort(sort.Reverse(sort.StringSlice(tempFiles)))
for _, path := range tempFiles {
var fi io.Writer
if fi, err = zw.Create(path); err != nil {
break
}
if n, err = fi.Write(f.readBytes(path)); int64(n) > math.MaxUint32 {
f.zip64Entries = append(f.zip64Entries, path)
}
}
return err
}
// writeZip64LFH function sets the ZIP version to 0x2D (45) in the Local File
// Header (LFH). Excel strictly enforces ZIP64 format validation rules. When any
// file within the workbook (OCP) exceeds 4GB in size, the ZIP64 format must be
// used according to the PKZIP specification. However, ZIP files generated using
// Go's standard archive/zip library always set the version in the local file
// header to 20 (ZIP version 2.0) by default, as defined in the internal
// 'writeHeader' function during ZIP creation. The archive/zip package only sets
// the 'ReaderVersion' to 45 (ZIP64 version 4.5) in the central directory for
// entries larger than 4GB. This results in a version mismatch between the
// central directory and the local file header. As a result, opening the
// generated workbook with spreadsheet application will prompt file corruption.
func (f *File) writeZip64LFH(buf *bytes.Buffer) error {
if len(f.zip64Entries) == 0 {
return nil
}
data, offset := buf.Bytes(), 0
for offset < len(data) {
idx := bytes.Index(data[offset:], []byte{0x50, 0x4b, 0x03, 0x04})
if idx == -1 {
break
}
idx += offset
if idx+30 > len(data) {
break
}
filenameLen := int(binary.LittleEndian.Uint16(data[idx+26 : idx+28]))
if idx+30+filenameLen > len(data) {
break
}
if inStrSlice(f.zip64Entries, string(data[idx+30:idx+30+filenameLen]), true) != -1 {
binary.LittleEndian.PutUint16(data[idx+4:idx+6], 45)
}
offset = idx + 1
}
return nil
}
================================================
FILE: file_test.go
================================================
package excelize
import (
"archive/zip"
"bufio"
"bytes"
"encoding/binary"
"errors"
"io"
"io/fs"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// errZipWriter is a mock ZipWriter whose Create and Close can be configured to
// return errors.
type errZipWriter struct {
createFunc func(string) (io.Writer, error)
closeErr error
}
func (m *errZipWriter) Create(name string) (io.Writer, error) {
if m.createFunc != nil {
return m.createFunc(name)
}
return &bytes.Buffer{}, nil
}
func (m *errZipWriter) AddFS(fs.FS) error { return nil }
func (m *errZipWriter) Close() error { return m.closeErr }
type errWriter struct{ err error }
func (e *errWriter) Write([]byte) (int, error) { return 0, e.err }
func BenchmarkWrite(b *testing.B) {
const s = "This is test data"
for b.Loop() {
f := NewFile()
for row := 1; row <= 10000; row++ {
for col := 1; col <= 20; col++ {
val, err := CoordinatesToCellName(col, row)
if err != nil {
b.Error(err)
}
if err := f.SetCellValue("Sheet1", val, s); err != nil {
b.Error(err)
}
}
}
// Save spreadsheet by the given path.
if err := f.SaveAs("test.xlsx"); err != nil {
b.Error(err)
}
}
}
func TestWriteTo(t *testing.T) {
// Test WriteToBuffer err
{
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) })
f.Pkg.Store("/d/", []byte("s"))
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory")
f.Pkg.Delete("/d/")
}
// Test file path overflow
{
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) })
const maxUint16 = 1<<16 - 1
f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
}
// Test StreamsWriter err
{
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) })
f.Pkg.Store("s", nil)
f.streams = make(map[string]*StreamWriter)
file, _ := os.Open("123")
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}}
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.Nil(t, err)
}
// Test write with temporary file
{
f, buf := File{tempFiles: sync.Map{}}, bytes.Buffer{}
f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) })
const maxUint16 = 1<<16 - 1
f.tempFiles.Store("s", "")
f.tempFiles.Store(strings.Repeat("s", maxUint16+1), "")
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
}
// Test write with unsupported workbook file format
{
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
f.SetZipWriter(func(w io.Writer) ZipWriter { return zip.NewWriter(w) })
f.Pkg.Store("/d", []byte("s"))
f.Path = "Book1.xls"
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
}
// Test write with unsupported charset content types.
{
f, buf := NewFile(), bytes.Buffer{}
f.ContentTypes, f.Path = nil, filepath.Join("test", "TestWriteTo.xlsx")
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
// Test WriteToBuffer with ZipWriter Close error
{
f := NewFile()
f.SetZipWriter(func(w io.Writer) ZipWriter {
return &errZipWriter{closeErr: errors.New("close error")}
})
_, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{}))
assert.EqualError(t, err, "close error")
}
// Test writeToZip with stream Create error
{
f := NewFile()
f.streams = make(map[string]*StreamWriter)
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{}}
f.SetZipWriter(func(w io.Writer) ZipWriter {
return &errZipWriter{
createFunc: func(name string) (io.Writer, error) {
if name == "s" {
return nil, errors.New("create stream error")
}
return &bytes.Buffer{}, nil
},
}
})
_, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{}))
assert.EqualError(t, err, "create stream error")
}
// Test writeToZip with stream rawData.Reader() error
{
f := NewFile()
f.streams = make(map[string]*StreamWriter)
tmp, err := os.CreateTemp("", "excelize-test-*")
assert.NoError(t, err)
assert.NoError(t, tmp.Close())
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: tmp}}
_, err = f.WriteTo(bufio.NewWriter(&bytes.Buffer{}))
assert.Error(t, err)
}
// Test writeToZip with io.Copy error on stream
{
f := NewFile()
f.streams = make(map[string]*StreamWriter)
sw := &StreamWriter{}
sw.rawData.buf.WriteString("test data")
f.streams["s"] = sw
f.SetZipWriter(func(w io.Writer) ZipWriter {
return &errZipWriter{
createFunc: func(name string) (io.Writer, error) {
if name == "s" {
return &errWriter{err: errors.New("copy error")}, nil
}
return &bytes.Buffer{}, nil
},
}
})
_, err := f.WriteTo(bufio.NewWriter(&bytes.Buffer{}))
assert.EqualError(t, err, "copy error")
}
}
func TestClose(t *testing.T) {
f := NewFile()
f.tempFiles.Store("/d/", "/d/")
require.Error(t, f.Close())
}
func TestZip64(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
sw, err := f.NewStreamWriter("Sheet1")
assert.NoError(t, err)
for r := range 131 {
rowData := make([]interface{}, 1000)
for c := range 1000 {
rowData[c] = strings.Repeat("c", TotalCellChars)
}
cell, err := CoordinatesToCellName(1, r+1)
assert.NoError(t, err)
assert.NoError(t, sw.SetRow(cell, rowData))
}
assert.NoError(t, sw.Flush())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestZip64.xlsx")))
assert.NoError(t, f.Close())
// Test with filename length overflow
f = NewFile()
f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings)
buf := new(bytes.Buffer)
buf.Write([]byte{0x50, 0x4b, 0x03, 0x04})
buf.Write(make([]byte, 20))
assert.NoError(t, f.writeZip64LFH(buf))
// Test with file header less than the required 30 for the fixed header part
f = NewFile()
f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings)
buf.Reset()
buf.Write([]byte{0x50, 0x4b, 0x03, 0x04})
buf.Write(make([]byte, 22))
assert.NoError(t, binary.Write(buf, binary.LittleEndian, uint16(10)))
buf.Write(make([]byte, 2))
buf.WriteString("test")
assert.NoError(t, f.writeZip64LFH(buf))
t.Run("for_save_zip64_with_in_memory_file_over_4GB", func(t *testing.T) {
// Test save workbook in ZIP64 format with in memory file with size over 4GB.
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", make([]byte, math.MaxUint32+1))
_, err := f.WriteToBuffer()
assert.NoError(t, err)
assert.NoError(t, f.Close())
})
t.Run("for_save_zip64_with_in_temporary_file_over_4GB", func(t *testing.T) {
// Test save workbook in ZIP64 format with temporary file with size over 4GB.
if os.Getenv("GITHUB_ACTIONS") == "true" {
t.Skip()
}
f := NewFile()
f.Pkg.Delete("xl/worksheets/sheet1.xml")
f.Sheet.Delete("xl/worksheets/sheet1.xml")
tmp, err := os.CreateTemp(os.TempDir(), "excelize-")
assert.NoError(t, err)
assert.NoError(t, tmp.Truncate(math.MaxUint32+1))
f.tempFiles.Store("xl/worksheets/sheet1.xml", tmp.Name())
assert.NoError(t, tmp.Close())
_, err = f.WriteToBuffer()
assert.NoError(t, err)
assert.NoError(t, f.Close())
})
}
func TestRemoveTempFiles(t *testing.T) {
tmp, err := os.CreateTemp("", "excelize-*")
if err != nil {
t.Fatal(err)
}
tmpName := tmp.Name()
assert.NoError(t, tmp.Close())
f := NewFile()
// Fill the tempFiles map with non-existing files
for i := range 1000 {
f.tempFiles.Store(strconv.Itoa(i), "/hopefully not existing")
}
f.tempFiles.Store("existing", tmpName)
require.Error(t, f.Close())
if _, err := os.Stat(tmpName); err == nil {
t.Errorf("temp file %q still exist", tmpName)
assert.NoError(t, os.Remove(tmpName))
}
}
================================================
FILE: go.mod
================================================
module github.com/xuri/excelize/v2
go 1.25.0
require (
github.com/richardlehane/mscfb v1.0.6
github.com/stretchr/testify v1.11.1
github.com/tiendc/go-deepcopy v1.7.2
github.com/xuri/efp v0.0.1
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9
golang.org/x/crypto v0.49.0
golang.org/x/image v0.25.0
golang.org/x/net v0.52.0
golang.org/x/text v0.35.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: hsl.go
================================================
// Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package excelize
import (
"image/color"
"math"
)
// HSLModel converts any color.Color to a HSL color.
var HSLModel = color.ModelFunc(hslModel)
// HSL represents a cylindrical coordinate of points in an RGB color model.
//
// Values are in the range 0 to 1.
type HSL struct {
H, S, L float64
}
// RGBA returns the alpha-premultiplied red, green, blue and alpha values
// for the HSL.
func (c HSL) RGBA() (uint32, uint32, uint32, uint32) {
r, g, b := HSLToRGB(c.H, c.S, c.L)
return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff
}
// hslModel converts a color.Color to HSL.
func hslModel(c color.Color) color.Color {
if _, ok := c.(HSL); ok {
return c
}
r, g, b, _ := c.RGBA()
h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8))
return HSL{h, s, l}
}
// RGBToHSL converts an RGB triple to an HSL triple.
func RGBToHSL(r, g, b uint8) (h, s, l float64) {
fR := float64(r) / 255
fG := float64(g) / 255
fB := float64(b) / 255
maxVal := math.Max(math.Max(fR, fG), fB)
minVal := math.Min(math.Min(fR, fG), fB)
l = (maxVal + minVal) / 2
if maxVal == minVal {
// Achromatic.
h, s = 0, 0
} else {
// Chromatic.
d := maxVal - minVal
if l > 0.5 {
s = d / (2.0 - maxVal - minVal)
} else {
s = d / (maxVal + minVal)
}
switch maxVal {
case fR:
h = (fG - fB) / d
if fG < fB {
h += 6
}
case fG:
h = (fB-fR)/d + 2
case fB:
h = (fR-fG)/d + 4
}
h /= 6
}
return
}
// HSLToRGB converts an HSL triple to an RGB triple.
func HSLToRGB(h, s, l float64) (r, g, b uint8) {
var fR, fG, fB float64
if s == 0 {
fR, fG, fB = l, l, l
} else {
var q float64
if l < 0.5 {
q = l * (1 + s)
} else {
q = l + s - s*l
}
p := 2*l - q
fR = hueToRGB(p, q, h+1.0/3)
fG = hueToRGB(p, q, h)
fB = hueToRGB(p, q, h-1.0/3)
}
r = uint8((fR * 255) + 0.5)
g = uint8((fG * 255) + 0.5)
b = uint8((fB * 255) + 0.5)
return
}
// hueToRGB is a helper function for HSLToRGB.
func hueToRGB(p, q, t float64) float64 {
if t < 0 {
t++
}
if t > 1 {
t--
}
if t < 1.0/6 {
return p + (q-p)*6*t
}
if t < 0.5 {
return q
}
if t < 2.0/3 {
return p + (q-p)*(2.0/3-t)*6
}
return p
}
================================================
FILE: lib.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"archive/zip"
"bytes"
"container/list"
"encoding/xml"
"fmt"
"io"
"math"
"math/big"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf16"
)
// ReadZipReader extract spreadsheet with given options.
func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
var (
err error
docPart = map[string]string{
"[content_types].xml": defaultXMLPathContentTypes,
"xl/sharedstrings.xml": defaultXMLPathSharedStrings,
}
fileList = make(map[string][]byte, len(r.File))
worksheets int
unzipSize int64
)
for _, v := range r.File {
fileSize := v.FileInfo().Size()
unzipSize += fileSize
if unzipSize > f.options.UnzipSizeLimit {
return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit)
}
fileName := strings.ReplaceAll(v.Name, "\\", "/")
if partName, ok := docPart[strings.ToLower(fileName)]; ok {
fileName = partName
}
if strings.EqualFold(fileName, defaultXMLPathSharedStrings) && fileSize > f.options.UnzipXMLSizeLimit {
tempFile, err := f.unzipToTemp(v)
if tempFile != "" {
f.tempFiles.Store(fileName, tempFile)
}
if err == nil {
continue
}
}
if strings.HasPrefix(strings.ToLower(fileName), "xl/worksheets/sheet") {
worksheets++
if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() {
tempFile, err := f.unzipToTemp(v)
if tempFile != "" {
f.tempFiles.Store(fileName, tempFile)
}
if err == nil {
continue
}
}
}
if fileList[fileName], err = readFile(v); err != nil {
return nil, 0, err
}
}
return fileList, worksheets, nil
}
// unzipToTemp unzip the zip entity to the system temporary directory and
// returned the unzipped file path.
func (f *File) unzipToTemp(zipFile *zip.File) (string, error) {
tmp, err := os.CreateTemp(f.options.TmpDir, "excelize-")
if err != nil {
return "", err
}
rc, err := zipFile.Open()
if err != nil {
return tmp.Name(), err
}
if _, err = io.Copy(tmp, rc); err != nil {
return tmp.Name(), err
}
if err = rc.Close(); err != nil {
return tmp.Name(), err
}
return tmp.Name(), tmp.Close()
}
// readXML provides a function to read XML content as bytes.
func (f *File) readXML(name string) []byte {
if content, _ := f.Pkg.Load(name); content != nil {
return content.([]byte)
}
if content, ok := f.streams[name]; ok {
return content.rawData.buf.Bytes()
}
return []byte{}
}
// readBytes read file as bytes by given path.
func (f *File) readBytes(name string) []byte {
content := f.readXML(name)
if len(content) != 0 {
return content
}
file, err := f.readTemp(name)
if err != nil {
return content
}
content, _ = io.ReadAll(file)
f.Pkg.Store(name, content)
_ = file.Close()
return content
}
// readTemp read file from system temporary directory by given path.
func (f *File) readTemp(name string) (file *os.File, err error) {
path, ok := f.tempFiles.Load(name)
if !ok {
return
}
file, err = os.Open(path.(string))
return
}
// saveFileList provides a function to update given file content in file list
// of spreadsheet.
func (f *File) saveFileList(name string, content []byte) {
f.Pkg.Store(name, append([]byte(xml.Header), content...))
}
// Read file content as string in an archive file.
func readFile(file *zip.File) ([]byte, error) {
rc, err := file.Open()
if err != nil {
return nil, err
}
dat := make([]byte, 0, file.FileInfo().Size())
buff := bytes.NewBuffer(dat)
_, _ = io.Copy(buff, rc)
return buff.Bytes(), rc.Close()
}
// SplitCellName splits cell name to column name and row number.
//
// Example:
//
// excelize.SplitCellName("AK74") // return "AK", 74, nil
func SplitCellName(cell string) (string, int, error) {
alpha := func(r rune) bool {
return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || (r == 36)
}
if strings.IndexFunc(cell, alpha) == 0 {
i := strings.LastIndexFunc(cell, alpha)
if i >= 0 && i < len(cell)-1 {
col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
if row, err := strconv.Atoi(rowStr); err == nil && row > 0 {
return col, row, nil
}
}
}
return "", -1, newInvalidCellNameError(cell)
}
// JoinCellName joins cell name from column name and row number.
func JoinCellName(col string, row int) (string, error) {
normCol := strings.Map(func(rune rune) rune {
switch {
case 'A' <= rune && rune <= 'Z':
return rune
case 'a' <= rune && rune <= 'z':
return rune - 32
}
return -1
}, col)
if len(col) == 0 || len(col) != len(normCol) {
return "", newInvalidColumnNameError(col)
}
if row < 1 {
return "", newInvalidRowNumberError(row)
}
return normCol + strconv.Itoa(row), nil
}
// ColumnNameToNumber provides a function to convert Excel sheet column name
// (case-insensitive) to int. The function returns an error if column name
// incorrect.
//
// Example:
//
// excelize.ColumnNameToNumber("AK") // returns 37, nil
func ColumnNameToNumber(name string) (int, error) {
if len(name) == 0 {
return -1, newInvalidColumnNameError(name)
}
col := 0
multi := 1
for i := len(name) - 1; i >= 0; i-- {
r := name[i]
if r >= 'A' && r <= 'Z' {
col += int(r-'A'+1) * multi
} else if r >= 'a' && r <= 'z' {
col += int(r-'a'+1) * multi
} else {
return -1, newInvalidColumnNameError(name)
}
multi *= 26
}
if col > MaxColumns {
return -1, ErrColumnNumber
}
return col, nil
}
// ColumnNumberToName provides a function to convert the integer to Excel
// sheet column title.
//
// Example:
//
// excelize.ColumnNumberToName(37) // returns "AK", nil
func ColumnNumberToName(num int) (string, error) {
if num < MinColumns || num > MaxColumns {
return "", ErrColumnNumber
}
estimatedLength := 0
for n := num; n > 0; n = (n - 1) / 26 {
estimatedLength++
}
result := make([]byte, estimatedLength)
for num > 0 {
estimatedLength--
result[estimatedLength] = byte((num-1)%26 + 'A')
num = (num - 1) / 26
}
return string(result), nil
}
// CellNameToCoordinates converts alphanumeric cell name to [X, Y] coordinates
// or returns an error.
//
// Example:
//
// excelize.CellNameToCoordinates("A1") // returns 1, 1, nil
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
func CellNameToCoordinates(cell string) (int, int, error) {
colName, row, err := SplitCellName(cell)
if err != nil {
return -1, -1, newCellNameToCoordinatesError(cell, err)
}
if row > TotalRows {
return -1, -1, ErrMaxRows
}
col, err := ColumnNameToNumber(colName)
return col, row, err
}
// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell
// name or returns an error.
//
// Example:
//
// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
// excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil
func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
if col < 1 || row < 1 {
return "", newCoordinatesToCellNameError(col, row)
}
if row > TotalRows {
return "", ErrMaxRows
}
sign := ""
for _, a := range abs {
if a {
sign = "$"
}
}
colName, err := ColumnNumberToName(col)
return sign + colName + sign + strconv.Itoa(row), err
}
// rangeRefToCoordinates provides a function to convert range reference to a
// pair of coordinates.
func rangeRefToCoordinates(ref string) ([]int, error) {
rng := strings.Split(strings.ReplaceAll(ref, "$", ""), ":")
if len(rng) < 2 {
return nil, ErrParameterInvalid
}
return cellRefsToCoordinates(rng[0], rng[1])
}
// cellRefsToCoordinates provides a function to convert cell range to a
// pair of coordinates.
func cellRefsToCoordinates(firstCell, lastCell string) ([]int, error) {
coordinates := make([]int, 4)
var err error
coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell)
if err != nil {
return coordinates, err
}
coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell)
return coordinates, err
}
// sortCoordinates provides a function to correct the cell range, such
// correct C1:B3 to B1:C3.
func sortCoordinates(coordinates []int) error {
if len(coordinates) != 4 {
return ErrCoordinates
}
if coordinates[2] < coordinates[0] {
coordinates[2], coordinates[0] = coordinates[0], coordinates[2]
}
if coordinates[3] < coordinates[1] {
coordinates[3], coordinates[1] = coordinates[1], coordinates[3]
}
return nil
}
// coordinatesToRangeRef provides a function to convert a pair of coordinates
// to range reference.
func coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) {
if len(coordinates) != 4 {
return "", ErrCoordinates
}
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...)
if err != nil {
return "", err
}
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...)
if err != nil {
return "", err
}
return firstCell + ":" + lastCell, err
}
// getDefinedNameRefTo convert defined name to reference range.
func (f *File) getDefinedNameRefTo(definedNameName, currentSheet string) (refTo string) {
var workbookRefTo, worksheetRefTo string
for _, definedName := range f.GetDefinedName() {
if definedName.Name == definedNameName {
// worksheet scope takes precedence over scope workbook when both definedNames exist
if definedName.Scope == "Workbook" {
workbookRefTo = definedName.RefersTo
}
if definedName.Scope == currentSheet {
worksheetRefTo = definedName.RefersTo
}
}
}
refTo = workbookRefTo
if worksheetRefTo != "" {
refTo = worksheetRefTo
}
return
}
// flatSqref convert reference sequence to cell reference list.
func flatSqref(sqref string) (cells map[int][][]int, err error) {
var coordinates []int
cells = make(map[int][][]int)
for _, ref := range strings.Fields(sqref) {
rng := strings.Split(ref, ":")
switch len(rng) {
case 1:
var col, row int
col, row, err = CellNameToCoordinates(rng[0])
if err != nil {
return
}
cells[col] = append(cells[col], []int{col, row})
case 2:
if coordinates, err = rangeRefToCoordinates(ref); err != nil {
return
}
_ = sortCoordinates(coordinates)
for c := coordinates[0]; c <= coordinates[2]; c++ {
for r := coordinates[1]; r <= coordinates[3]; r++ {
cells[c] = append(cells[c], []int{c, r})
}
}
}
}
// Sort each column's cells by row number
for col := range cells {
sort.Slice(cells[col], func(i, j int) bool {
return cells[col][i][1] < cells[col][j][1]
})
}
return
}
// inCoordinates provides a method to check if a coordinate is present in
// coordinates array, and return the index of its location, otherwise
// return -1.
func inCoordinates(a [][]int, x []int) int {
for idx, n := range a {
if x[0] == n[0] && x[1] == n[1] {
return idx
}
}
return -1
}
// inStrSlice provides a method to check if an element is present in an array,
// and return the index of its location, otherwise return -1.
func inStrSlice(a []string, x string, caseSensitive bool) int {
for idx, n := range a {
if !caseSensitive && strings.EqualFold(x, n) {
return idx
}
if x == n {
return idx
}
}
return -1
}
// inFloat64Slice provides a method to check if an element is present in a
// float64 array, and return the index of its location, otherwise return -1.
func inFloat64Slice(a []float64, x float64) int {
for idx, n := range a {
if x == n {
return idx
}
}
return -1
}
// boolPtr returns a pointer to a bool with the given value.
func boolPtr(b bool) *bool { return &b }
// intPtr returns a pointer to an int with the given value.
func intPtr(i int) *int { return &i }
// uintPtr returns a pointer to an unsigned integer with the given value.
func uintPtr(u uint) *uint { return &u }
// float64Ptr returns a pointer to a float64 with the given value.
func float64Ptr(f float64) *float64 { return &f }
// stringPtr returns a pointer to a string with the given value.
func stringPtr(s string) *string { return &s }
// Value extracts float64 data type numeric from a attribute value.
func (attr *attrValFloat) Value() float64 {
if attr != nil && attr.Val != nil {
return *attr.Val
}
return 0
}
// Value extracts boolean data type value from a attribute value.
func (avb *attrValBool) Value() bool {
if avb != nil && avb.Val != nil {
return *avb.Val
}
return false
}
// Value extracts string data type text from a attribute value.
func (avb *attrValString) Value() string {
if avb != nil && avb.Val != nil {
return *avb.Val
}
return ""
}
// MarshalXML convert the boolean data type to literal values 0 or 1 on
// serialization.
func (avb attrValBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
attr := xml.Attr{
Name: xml.Name{
Space: start.Name.Space,
Local: "val",
},
Value: "0",
}
if avb.Val != nil {
if *avb.Val {
attr.Value = "1"
} else {
attr.Value = "0"
}
}
start.Attr = []xml.Attr{attr}
if err := e.EncodeToken(start); err != nil {
return err
}
return e.EncodeToken(start.End())
}
// UnmarshalXML convert the literal values true, false, 1, 0 of the XML
// attribute to boolean data type on deserialization.
func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := d.Token()
if err != nil {
return err
}
found := false
switch t.(type) {
case xml.StartElement:
return ErrAttrValBool
case xml.EndElement:
found = true
}
if found {
break
}
}
for _, attr := range start.Attr {
if attr.Name.Local == "val" {
if attr.Value == "" {
val := true
avb.Val = &val
} else {
val, err := strconv.ParseBool(attr.Value)
if err != nil {
return err
}
avb.Val = &val
}
return nil
}
}
defaultVal := true
avb.Val = &defaultVal
return nil
}
// MarshalXML encodes ext element with specified namespace attributes on
// serialization.
func (ext xlsxExt) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
start.Attr = ext.xmlns
return e.EncodeElement(decodeExt{URI: ext.URI, Content: ext.Content}, start)
}
// UnmarshalXML extracts ext element attributes namespace by giving XML decoder
// on deserialization.
func (ext *xlsxExt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for _, attr := range start.Attr {
if attr.Name.Local == "uri" {
continue
}
if attr.Name.Space == "xmlns" {
attr.Name.Space = ""
attr.Name.Local = "xmlns:" + attr.Name.Local
}
ext.xmlns = append(ext.xmlns, attr)
}
e := &decodeExt{}
if err := d.DecodeElement(&e, &start); err != nil {
return err
}
ext.URI, ext.Content = e.URI, e.Content
return nil
}
// namespaceStrictToTransitional provides a method to convert Strict and
// Transitional namespaces.
func namespaceStrictToTransitional(content []byte) []byte {
namespaceTranslationDic := map[string]string{
StrictNameSpaceDocumentPropertiesVariantTypes: NameSpaceDocumentPropertiesVariantTypes.Value,
StrictNameSpaceDrawingMLMain: NameSpaceDrawingMLMain,
StrictNameSpaceExtendedProperties: NameSpaceExtendedProperties,
StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet.Value,
StrictSourceRelationship: SourceRelationship.Value,
StrictSourceRelationshipChart: SourceRelationshipChart,
StrictSourceRelationshipComments: SourceRelationshipComments,
StrictSourceRelationshipExtendProperties: SourceRelationshipExtendProperties,
StrictSourceRelationshipImage: SourceRelationshipImage,
StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument,
}
for s, n := range namespaceTranslationDic {
content = bytesReplace(content, []byte(s), []byte(n), -1)
}
return content
}
// bytesReplace replace source bytes with given target.
func bytesReplace(s, source, target []byte, n int) []byte {
if n == 0 {
return s
}
if len(source) < len(target) {
return bytes.Replace(s, source, target, n)
}
if n < 0 {
n = len(s)
}
var wid, i, j, w int
for i, j = 0, 0; i < len(s) && j < n; j++ {
wid = bytes.Index(s[i:], source)
if wid < 0 {
break
}
w += copy(s[w:], s[i:i+wid])
w += copy(s[w:], target)
i += wid + len(source)
}
w += copy(s[w:], s[i:])
return s[:w]
}
// genSheetPasswd provides a method to generate password for worksheet
// protection by given plaintext. When an Excel sheet is being protected with
// a password, a 16-bit (two byte) long hash is generated. To verify a
// password, it is compared to the hash. Obviously, if the input data volume
// is great, numerous passwords will match the same hash. Here is the
// algorithm to create the hash value:
//
// take the ASCII values of all characters shift left the first character 1 bit,
// the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits,
// the highest bit of the 16-bit value is always 0 [signed short])
// XOR all these values
// XOR the count of characters
// XOR the constant 0xCE4B
func genSheetPasswd(plaintext string) string {
var password int64 = 0x0000
var charPos uint = 1
for _, v := range plaintext {
value := int64(v) << charPos
charPos++
rotatedBits := value >> 15 // rotated bits beyond bit 15
value &= 0x7fff // first 15 bits
password ^= value | rotatedBits
}
password ^= int64(len(plaintext))
password ^= 0xCE4B
return strings.ToUpper(strconv.FormatInt(password, 16))
}
// getRootElement extract root element attributes by given XML decoder.
func getRootElement(d *xml.Decoder) []xml.Attr {
tokenIdx := 0
for {
token, _ := d.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
tokenIdx++
if tokenIdx == 1 {
var ns bool
for i := 0; i < len(startElement.Attr); i++ {
if startElement.Attr[i].Value == NameSpaceSpreadSheet.Value &&
startElement.Attr[i].Name == NameSpaceSpreadSheet.Name {
ns = true
}
}
if !ns {
startElement.Attr = append(startElement.Attr, NameSpaceSpreadSheet)
}
return startElement.Attr
}
}
}
return nil
}
// genXMLNamespace generate serialized XML attributes with a multi namespace
// by given element attributes.
func genXMLNamespace(attr []xml.Attr) string {
var rootElement string
for _, v := range attr {
if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" {
if lastSpace == NameSpaceXML {
lastSpace = "xml"
}
rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value)
continue
}
rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value)
}
return strings.TrimSpace(rootElement) + ">"
}
// getXMLNamespace extract XML namespace from specified element name and attributes.
func getXMLNamespace(space string, attr []xml.Attr) string {
for _, attribute := range attr {
if attribute.Value == space {
return attribute.Name.Local
}
}
return space
}
// replaceNameSpaceBytes provides a function to replace the XML root element
// attribute by the given component part path and XML content.
func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte {
sourceXmlns := []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
targetXmlns := []byte(templateNamespaceIDMap)
if attrs, ok := f.xmlAttr.Load(path); ok {
targetXmlns = []byte(genXMLNamespace(attrs.([]xml.Attr)))
}
return bytesReplace(contentMarshal, sourceXmlns, bytes.ReplaceAll(targetXmlns, []byte(" mc:Ignorable=\"r\""), []byte{}), -1)
}
// addNameSpaces provides a function to add an XML attribute by the given
// component part path.
func (f *File) addNameSpaces(path string, ns xml.Attr) {
exist := false
mc := false
ignore := -1
if attrs, ok := f.xmlAttr.Load(path); ok {
for i, attr := range attrs.([]xml.Attr) {
if attr.Name.Local == ns.Name.Local && attr.Name.Space == ns.Name.Space {
exist = true
}
if attr.Name.Local == "Ignorable" && getXMLNamespace(attr.Name.Space, attrs.([]xml.Attr)) == "mc" {
ignore = i
}
if attr.Name.Local == "mc" && attr.Name.Space == "xmlns" {
mc = true
}
}
}
if !exist {
attrs, _ := f.xmlAttr.Load(path)
if attrs == nil {
attrs = []xml.Attr{}
}
attrs = append(attrs.([]xml.Attr), ns)
f.xmlAttr.Store(path, attrs)
if !mc {
attrs = append(attrs.([]xml.Attr), SourceRelationshipCompatibility)
f.xmlAttr.Store(path, attrs)
}
if ignore == -1 {
attrs = append(attrs.([]xml.Attr), xml.Attr{
Name: xml.Name{Local: "Ignorable", Space: "mc"},
Value: ns.Name.Local,
})
f.xmlAttr.Store(path, attrs)
return
}
f.setIgnorableNameSpace(path, ignore, ns)
}
}
// setIgnorableNameSpace provides a function to set XML namespace as ignorable
// by the given attribute.
func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) {
ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"}
xmlAttrs, _ := f.xmlAttr.Load(path)
if inStrSlice(strings.Fields(xmlAttrs.([]xml.Attr)[index].Value), ns.Name.Local, true) == -1 && inStrSlice(ignorableNS, ns.Name.Local, true) != -1 {
xmlAttrs.([]xml.Attr)[index].Value = strings.TrimSpace(fmt.Sprintf("%s %s", xmlAttrs.([]xml.Attr)[index].Value, ns.Name.Local))
f.xmlAttr.Store(path, xmlAttrs)
}
}
// addSheetNameSpace add XML attribute for worksheet.
func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
name, _ := f.getSheetXMLPath(sheet)
f.addNameSpaces(name, ns)
}
// isNumeric determines whether an expression is a valid numeric type and get
// the precision for the numeric.
func isNumeric(s string) (bool, int, float64) {
if strings.Contains(s, "_") {
return false, 0, 0
}
var decimal big.Float
_, ok := decimal.SetString(s)
if !ok {
return false, 0, 0
}
var noScientificNotation string
flt, _ := decimal.Float64()
noScientificNotation = strconv.FormatFloat(flt, 'f', -1, 64)
return true, len(strings.ReplaceAll(noScientificNotation, ".", "")), flt
}
var (
bstrExp = regexp.MustCompile(`_x[a-fA-F\d]{4}_`)
bstrEscapeExp = regexp.MustCompile(`x[a-fA-F\d]{4}_`)
)
// bstrUnmarshal parses the binary basic string, this will trim escaped string
// literal which not permitted in an XML 1.0 document. The basic string
// variant type can store any valid Unicode character. Unicode's characters
// that cannot be directly represented in XML as defined by the XML 1.0
// specification, shall be escaped using the Unicode numerical character
// representation escape character format _xHHHH_, where H represents a
// hexadecimal character in the character's value. For example: The Unicode
// character 8 is not permitted in an XML 1.0 document, so it shall be
// escaped as _x0008_. To store the literal form of an escape sequence, the
// initial underscore shall itself be escaped (i.e. stored as _x005F_). For
// example: The string literal _x0008_ would be stored as _x005F_x0008_.
func bstrUnmarshal(s string) (result string) {
matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0
for _, match := range matches {
result += s[cursor:match[0]]
subStr := s[match[0]:match[1]]
if subStr == "_x005F_" {
cursor = match[1]
result += "_"
continue
}
if bstrExp.MatchString(subStr) {
cursor = match[1]
v, _ := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`)
result += v
}
}
if cursor < l {
result += s[cursor:]
}
return result
}
// bstrMarshal encode the escaped string literal which not permitted in an XML
// 1.0 document.
func bstrMarshal(s string) (result string) {
matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0
for _, match := range matches {
result += s[cursor:match[0]]
subStr := s[match[0]:match[1]]
if subStr == "_x005F_" {
cursor = match[1]
if match[1]+6 <= l && bstrEscapeExp.MatchString(s[match[1]:match[1]+6]) {
_, err := strconv.Unquote(`"\u` + s[match[1]+1:match[1]+5] + `"`)
if err == nil {
result += subStr + "x005F" + subStr
continue
}
}
result += subStr + "x005F_"
continue
}
if bstrExp.MatchString(subStr) {
cursor = match[1]
if _, err := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`); err == nil {
result += "_x005F" + subStr
continue
}
}
}
if cursor < l {
result += s[cursor:]
}
return result
}
// floatToFraction converts a float number to a fraction string representation
// with specified placeholder widths for numerator and denominator.
func floatToFraction(x float64, numeratorPlaceHolder, denominatorPlaceHolder int) string {
if denominatorPlaceHolder <= 0 {
return ""
}
num, den := floatToFracUseContinuedFraction(x, int64(math.Pow10(denominatorPlaceHolder)))
if num == 0 {
return strings.Repeat(" ", numeratorPlaceHolder+denominatorPlaceHolder+1)
}
numStr := strconv.FormatInt(num, 10)
denStr := strconv.FormatInt(den, 10)
numeratorPlaceHolder = max(numeratorPlaceHolder-len(numStr), 0)
denominatorPlaceHolder = max(denominatorPlaceHolder-len(denStr), 0)
return fmt.Sprintf("%s%s/%s%s", strings.Repeat(" ", numeratorPlaceHolder), numStr, denStr, strings.Repeat(" ", denominatorPlaceHolder))
}
// floatToFracUseContinuedFraction implement convert a floating-point decimal
// to a fraction using continued fractions and recurrence relations.
func floatToFracUseContinuedFraction(r float64, denominatorLimit int64) (num, den int64) {
p1 := int64(1) // LaTex: p_{-1}
q1 := int64(0) // LaTex: q_{-1}
p2 := int64(0) // LaTex: p_{-2}
q2 := int64(1) // LaTex: q_{-2}
var lasta, lastb int64
var curra, currb int64
for k := 0; ; k++ {
// a_{k} = \lfloor r_{k} \rfloor
a := int64(math.Floor(r))
// Fundamental recurrence formulas: p_{k} = a_{k} \cdot p_{k-1} + p_{k-2}
curra, currb = a*p1+p2, a*q1+q2
p2 = p1
q2 = q1
p1 = curra
q1 = currb
frac := r - float64(a)
if q1 >= denominatorLimit {
return lasta, lastb
}
if math.Abs(frac) < 1e-12 {
// use safe floating-point number comparison. If the input(r) is a real number, here is 0.
return curra, currb
}
lasta, lastb = curra, currb
// r_{k+1} = \frac{1}{r_{k} - a_{k}}
r = 1.0 / frac
}
}
// assignFieldValue assigns the value from an immutable reflect.Value to a
// mutable reflect.Value based on the type of the immutable value.
func assignFieldValue(field string, immutable, mutable reflect.Value) {
switch immutable.Kind() {
case reflect.Bool:
mutable.FieldByName(field).SetBool(immutable.Bool())
case reflect.Int:
mutable.FieldByName(field).SetInt(immutable.Int())
default:
mutable.FieldByName(field).SetString(immutable.String())
}
}
// setPtrFields assigns the fields of the immutable struct to the mutable
// struct. The fields name of the immutable struct must match the field names of
// the mutable struct.
func setPtrFields(immutable, mutable reflect.Value) {
for i := range immutable.NumField() {
srcField := immutable.Type().Field(i)
dstField := mutable.FieldByName(srcField.Name)
if dstField.IsValid() && dstField.CanSet() && dstField.Type() == immutable.Field(i).Type() {
dstField.Set(immutable.Field(i))
}
}
}
// setNoPtrFieldsVal assigns values from the pointer or no-pointer structs
// fields (immutable) value to no-pointer struct field.
func setNoPtrFieldsVal(fields []string, immutable, mutable reflect.Value) {
for _, field := range fields {
immutableField := immutable.FieldByName(field)
if immutableField.Kind() == reflect.Ptr {
if immutableField.IsValid() && !immutableField.IsNil() {
assignFieldValue(field, immutableField.Elem(), mutable)
}
continue
}
assignFieldValue(field, immutableField, mutable)
}
}
// setPtrFieldsVal assigns values from the pointer or no-pointer structs
// fields (immutable) value to pointer struct field.
func setPtrFieldsVal(fields []string, immutable, mutable reflect.Value) {
for _, field := range fields {
immutableField := immutable.FieldByName(field)
if immutableField.Kind() == reflect.Ptr {
if immutableField.IsValid() && !immutableField.IsNil() {
mutable.FieldByName(field).Set(immutableField.Elem())
}
continue
}
if immutableField.IsZero() {
continue
}
ptr := reflect.New(immutableField.Type())
ptr.Elem().Set(immutableField)
mutable.FieldByName(field).Set(ptr)
}
}
// countUTF16String returns the number of UTF-16 code units in a string.
func countUTF16String(s string) int {
var cnt int
for _, r := range s {
cnt += utf16.RuneLen(r)
}
return cnt
}
// truncateUTF16Units truncates a string to a maximum number of UTF-16 code
// units.
func truncateUTF16Units(s string, length int) string {
var cnt int
for i, r := range s {
if cnt += utf16.RuneLen(r); cnt > length {
return s[:i]
}
}
return s
}
// Stack defined an abstract data type that serves as a collection of elements.
type Stack struct {
list *list.List
}
// NewStack create a new stack.
func NewStack() *Stack {
l := list.New()
return &Stack{l}
}
// Push a value onto the top of the stack.
func (stack *Stack) Push(value interface{}) {
stack.list.PushBack(value)
}
// Pop the top item of the stack and return it.
func (stack *Stack) Pop() interface{} {
e := stack.list.Back()
if e != nil {
stack.list.Remove(e)
return e.Value
}
return nil
}
// Peek view the top item on the stack.
func (stack *Stack) Peek() interface{} {
e := stack.list.Back()
if e != nil {
return e.Value
}
return nil
}
// Len return the number of items in the stack.
func (stack *Stack) Len() int {
return stack.list.Len()
}
// Empty the stack.
func (stack *Stack) Empty() bool {
return stack.list.Len() == 0
}
================================================
FILE: lib_test.go
================================================
package excelize
import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var validColumns = []struct {
Name string
Num int
}{
{Name: "A", Num: 1},
{Name: "Z", Num: 26},
{Name: "AA", Num: 26 + 1},
{Name: "AK", Num: 26 + 11},
{Name: "ak", Num: 26 + 11},
{Name: "Ak", Num: 26 + 11},
{Name: "aK", Num: 26 + 11},
{Name: "AZ", Num: 26 + 26},
{Name: "ZZ", Num: 26 + 26*26},
{Name: "AAA", Num: 26 + 26*26 + 1},
}
var invalidColumns = []struct {
Name string
Num int
}{
{Name: "", Num: -1},
{Name: " ", Num: -1},
{Name: "_", Num: -1},
{Name: "__", Num: -1},
{Name: "-1", Num: -1},
{Name: "0", Num: -1},
{Name: " A", Num: -1},
{Name: "A ", Num: -1},
{Name: "A1", Num: -1},
{Name: "1A", Num: -1},
{Name: " a", Num: -1},
{Name: "a ", Num: -1},
{Name: "a1", Num: -1},
{Name: "1a", Num: -1},
{Name: " _", Num: -1},
{Name: "_ ", Num: -1},
{Name: "_1", Num: -1},
{Name: "1_", Num: -1},
}
var invalidCells = []string{"", "A", "AA", " A", "A ", "1A", "A1A", "A1 ", " A1", "1A1", "a-1", "A-1"}
var invalidIndexes = []int{-100, -2, -1, 0}
func TestColumnNameToNumber_OK(t *testing.T) {
const msg = "Column %q"
for _, col := range validColumns {
out, err := ColumnNameToNumber(col.Name)
if assert.NoErrorf(t, err, msg, col.Name) {
assert.Equalf(t, col.Num, out, msg, col.Name)
}
}
}
func TestColumnNameToNumber_Error(t *testing.T) {
const msg = "Column %q"
for _, col := range invalidColumns {
out, err := ColumnNameToNumber(col.Name)
if assert.Errorf(t, err, msg, col.Name) {
assert.Equalf(t, col.Num, out, msg, col.Name)
}
}
_, err := ColumnNameToNumber("XFE")
assert.ErrorIs(t, err, ErrColumnNumber)
}
func TestColumnNumberToName_OK(t *testing.T) {
const msg = "Column %q"
for _, col := range validColumns {
out, err := ColumnNumberToName(col.Num)
if assert.NoErrorf(t, err, msg, col.Name) {
assert.Equalf(t, strings.ToUpper(col.Name), out, msg, col.Name)
}
}
}
func TestColumnNumberToName_Error(t *testing.T) {
out, err := ColumnNumberToName(-1)
if assert.Error(t, err) {
assert.Empty(t, out)
}
out, err = ColumnNumberToName(0)
if assert.Error(t, err) {
assert.Empty(t, out)
}
_, err = ColumnNumberToName(MaxColumns + 1)
assert.ErrorIs(t, err, ErrColumnNumber)
}
func TestSplitCellName_OK(t *testing.T) {
const msg = "Cell \"%s%d\""
for i, col := range validColumns {
row := i + 1
c, r, err := SplitCellName(col.Name + strconv.Itoa(row))
if assert.NoErrorf(t, err, msg, col.Name, row) {
assert.Equalf(t, col.Name, c, msg, col.Name, row)
assert.Equalf(t, row, r, msg, col.Name, row)
}
}
}
func TestSplitCellName_Error(t *testing.T) {
const msg = "Cell %q"
for _, cell := range invalidCells {
c, r, err := SplitCellName(cell)
if assert.Errorf(t, err, msg, cell) {
assert.Equalf(t, "", c, msg, cell)
assert.Equalf(t, -1, r, msg, cell)
}
}
}
func TestJoinCellName_OK(t *testing.T) {
const msg = "Cell \"%s%d\""
for i, col := range validColumns {
row := i + 1
cell, err := JoinCellName(col.Name, row)
if assert.NoErrorf(t, err, msg, col.Name, row) {
assert.Equalf(t, strings.ToUpper(fmt.Sprintf("%s%d", col.Name, row)), cell, msg, row)
}
}
}
func TestJoinCellName_Error(t *testing.T) {
const msg = "Cell \"%s%d\""
test := func(col string, row int) {
cell, err := JoinCellName(col, row)
if assert.Errorf(t, err, msg, col, row) {
assert.Equalf(t, "", cell, msg, col, row)
}
}
for _, col := range invalidColumns {
test(col.Name, 1)
for _, row := range invalidIndexes {
test("A", row)
test(col.Name, row)
}
}
}
func TestCellNameToCoordinates_OK(t *testing.T) {
const msg = "Cell \"%s%d\""
for i, col := range validColumns {
row := i + 1
c, r, err := CellNameToCoordinates(col.Name + strconv.Itoa(row))
if assert.NoErrorf(t, err, msg, col.Name, row) {
assert.Equalf(t, col.Num, c, msg, col.Name, row)
assert.Equalf(t, i+1, r, msg, col.Name, row)
}
}
}
func TestCellNameToCoordinates_Error(t *testing.T) {
const msg = "Cell %q"
for _, cell := range invalidCells {
c, r, err := CellNameToCoordinates(cell)
if assert.Errorf(t, err, msg, cell) {
assert.Equalf(t, -1, c, msg, cell)
assert.Equalf(t, -1, r, msg, cell)
}
}
_, _, err := CellNameToCoordinates("A1048577")
assert.EqualError(t, err, ErrMaxRows.Error())
}
func TestCoordinatesToCellName_OK(t *testing.T) {
const msg = "Coordinates [%d, %d]"
for i, col := range validColumns {
row := i + 1
cell, err := CoordinatesToCellName(col.Num, row)
if assert.NoErrorf(t, err, msg, col.Num, row) {
assert.Equalf(t, strings.ToUpper(col.Name+strconv.Itoa(row)), cell, msg, col.Num, row)
}
}
}
func TestCoordinatesToCellName_Error(t *testing.T) {
const msg = "Coordinates [%d, %d]"
test := func(col, row int) {
cell, err := CoordinatesToCellName(col, row)
if assert.Errorf(t, err, msg, col, row) {
assert.Equalf(t, "", cell, msg, col, row)
}
}
for _, col := range invalidIndexes {
test(col, 1)
for _, row := range invalidIndexes {
test(1, row)
test(col, row)
}
}
}
func TestCoordinatesToRangeRef(t *testing.T) {
_, err := coordinatesToRangeRef([]int{})
assert.EqualError(t, err, ErrCoordinates.Error())
_, err = coordinatesToRangeRef([]int{1, -1, 1, 1})
assert.Equal(t, newCoordinatesToCellNameError(1, -1), err)
_, err = coordinatesToRangeRef([]int{1, 1, 1, -1})
assert.Equal(t, newCoordinatesToCellNameError(1, -1), err)
ref, err := coordinatesToRangeRef([]int{1, 1, 1, 1})
assert.NoError(t, err)
assert.EqualValues(t, ref, "A1:A1")
}
func TestSortCoordinates(t *testing.T) {
assert.EqualError(t, sortCoordinates(make([]int, 3)), ErrCoordinates.Error())
}
func TestInStrSlice(t *testing.T) {
assert.EqualValues(t, -1, inStrSlice([]string{}, "", true))
}
func TestAttrValue(t *testing.T) {
assert.Empty(t, (&attrValString{}).Value())
assert.False(t, (&attrValBool{}).Value())
assert.Zero(t, (&attrValFloat{}).Value())
}
func TestBoolValMarshal(t *testing.T) {
bold := true
node := &xlsxFont{B: &attrValBool{Val: &bold}}
data, err := xml.Marshal(node)
assert.NoError(t, err)
assert.Equal(t, ``, string(data))
node = &xlsxFont{}
err = xml.Unmarshal(data, node)
assert.NoError(t, err)
assert.NotEqual(t, nil, node)
assert.NotEqual(t, nil, node.B)
assert.NotEqual(t, nil, node.B.Val)
assert.Equal(t, true, *node.B.Val)
}
func TestBoolValUnmarshalXML(t *testing.T) {
node := xlsxFont{}
assert.NoError(t, xml.Unmarshal([]byte(""), &node))
assert.Equal(t, true, *node.B.Val)
for content, err := range map[string]string{
"": "unexpected child of attrValBool",
"": "strconv.ParseBool: parsing \"x\": invalid syntax",
} {
assert.EqualError(t, xml.Unmarshal([]byte(content), &node), err)
}
attr := attrValBool{}
assert.EqualError(t, attr.UnmarshalXML(xml.NewDecoder(strings.NewReader("")), xml.StartElement{}), io.EOF.Error())
}
func TestExtUnmarshalXML(t *testing.T) {
f, extLst := NewFile(), decodeExtLst{}
expected := fmt.Sprintf(``,
ExtURISlicerCachesX14, NameSpaceSpreadSheetX14.Value)
assert.NoError(t, f.xmlNewDecoder(strings.NewReader(expected)).Decode(&extLst))
assert.Len(t, extLst.Ext, 1)
assert.Equal(t, extLst.Ext[0].URI, ExtURISlicerCachesX14)
}
func TestBytesReplace(t *testing.T) {
s := []byte{0x01}
assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0))
}
func TestGetRootElement(t *testing.T) {
assert.Len(t, getRootElement(xml.NewDecoder(strings.NewReader(""))), 0)
// Test get workbook root element which all workbook XML namespace has prefix
f := NewFile()
d := f.xmlNewDecoder(bytes.NewReader([]byte(``)))
assert.Len(t, getRootElement(d), 3)
}
func TestSetIgnorableNameSpace(t *testing.T) {
f := NewFile()
f.xmlAttr.Store("xml_path", []xml.Attr{{}})
f.setIgnorableNameSpace("xml_path", 0, xml.Attr{Name: xml.Name{Local: "c14"}})
attrs, ok := f.xmlAttr.Load("xml_path")
assert.EqualValues(t, "c14", attrs.([]xml.Attr)[0].Value)
assert.True(t, ok)
}
func TestStack(t *testing.T) {
s := NewStack()
assert.Equal(t, s.Peek(), nil)
assert.Equal(t, s.Pop(), nil)
}
func TestGenXMLNamespace(t *testing.T) {
assert.Equal(t, genXMLNamespace([]xml.Attr{
{Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve"},
}), `xml:space="preserve">`)
}
func TestBstrUnmarshal(t *testing.T) {
bstrs := map[string]string{
"*": "*",
"*_x0000_": "*\x00",
"*_x0008_": "*\b",
"_x0008_*": "\b*",
"*_x0008_*": "*\b*",
"*_x4F60__x597D_": "*你好",
"*_xG000_": "*_xG000_",
"*_xG05F_x0001_*": "*_xG05F\x01*",
"*_x005F__x0008_*": "*_\b*",
"*_x005F_x0001_*": "*_x0001_*",
"*_x005f_x005F__x0008_*": "*_x005F_\b*",
"*_x005F_x005F_xG05F_x0006_*": "*_x005F_xG05F\x06*",
"*_x005F_x005F_x005F_x0006_*": "*_x005F_x0006_*",
"_x005F__x0008_******": "_\b******",
"******_x005F__x0008_": "******_\b",
"******_x005F__x0008_******": "******_\b******",
"_x000x_x005F_x000x_": "_x000x_x000x_",
}
for bstr, expected := range bstrs {
assert.Equal(t, expected, bstrUnmarshal(bstr), bstr)
}
}
func TestBstrMarshal(t *testing.T) {
bstrs := map[string]string{
"*_xG05F_*": "*_xG05F_*",
"*_x0008_*": "*_x005F_x0008_*",
"*_x005F_*": "*_x005F_x005F_*",
"*_x005F_xG006_*": "*_x005F_x005F_xG006_*",
"*_x005F_x0006_*": "*_x005F_x005F_x005F_x0006_*",
}
for bstr, expected := range bstrs {
assert.Equal(t, expected, bstrMarshal(bstr))
}
}
func TestTruncateUTF16Units(t *testing.T) {
assertTrunc := func(s string, max int, expected string) {
assert.Equal(t, expected, truncateUTF16Units(s, max), "src=%q max=%d", s, max)
assert.LessOrEqual(t, countUTF16String(truncateUTF16Units(s, max)), max)
}
// No truncation
assertTrunc("ABC", 3, "ABC")
assertTrunc("A\U0001F600B", 4, "A\U0001F600B")
// Truncate cutting before BMP rune
assertTrunc("ABCDE", 3, "ABC")
// Truncate with surrogate pair boundary: keep pair intact
assertTrunc("A\U0001F600B", 3, "A\U0001F600") // 1 + 2 units
assertTrunc("A\U0001F600B", 2, "A") // pair would overflow
assertTrunc("\U0001F600B", 1, "") // first rune (2 units) exceeds limit
assertTrunc("\U0001F600B", 2, "\U0001F600") // exact fit
assertTrunc("\U0001F600B", 3, "\U0001F600B") // allow extra
// Multiple surrogate pairs
assertTrunc("\U0001F600\U0001F600B", 2, "\U0001F600") // corrected expectation per logic
assertTrunc("\U0001F600\U0001F600B", 3, "\U0001F600") // 2 units kept, next pair would exceed
assertTrunc("\U0001F600\U0001F600B", 4, "\U0001F600\U0001F600") // both pairs (4 units)
}
func TestReadBytes(t *testing.T) {
f := &File{tempFiles: sync.Map{}}
sheet := "xl/worksheets/sheet1.xml"
f.tempFiles.Store(sheet, "/d/")
assert.Equal(t, []byte{}, f.readBytes(sheet))
}
func TestUnzipToTemp(t *testing.T) {
assert.NoError(t, os.Setenv("TMPDIR", "test"))
defer func() {
assert.NoError(t, os.Unsetenv("TMPDIR"))
}()
assert.NoError(t, os.Chmod(os.TempDir(), 0o444))
f := NewFile()
data := []byte("PK\x03\x040000000PK\x01\x0200000" +
"0000000000000000000\x00" +
"\x00\x00\x00\x00\x00000000000000PK\x01" +
"\x020000000000000000000" +
"00000\v\x00\x00\x00\x00\x00000000000" +
"00000000000000PK\x01\x0200" +
"00000000000000000000" +
"00\v\x00\x00\x00\x00\x00000000000000" +
"00000000000PK\x01\x020000<" +
"0\x00\x0000000000000000\v\x00\v" +
"\x00\x00\x00\x00\x0000000000\x00\x00\x00\x00000" +
"00000000PK\x01\x0200000000" +
"0000000000000000\v\x00\x00\x00" +
"\x00\x0000PK\x05\x06000000\x05\x00\xfd\x00\x00\x00" +
"\v\x00\x00\x00\x00\x00")
z, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
_, err = f.unzipToTemp(z.File[0])
require.Error(t, err)
assert.NoError(t, os.Chmod(os.TempDir(), 0o755))
_, err = f.unzipToTemp(z.File[0])
assert.EqualError(t, err, "EOF")
}
func TestFloat2Frac(t *testing.T) {
assert.Empty(t, floatToFraction(0.19, 0, 0))
assert.Equal(t, "1/5", floatToFraction(0.19, 1, 1))
assert.Equal(t, "9999/10000", strings.Trim(floatToFraction(0.9999, 10, 10), " "))
assert.Equal(t, "954888175898973913/351283728530932463", floatToFraction(math.E, 1, 18))
}
================================================
FILE: merge.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "strings"
// Rect gets merged cell rectangle coordinates sequence.
func (mc *xlsxMergeCell) Rect() ([]int, error) {
var err error
if mc.rect == nil {
mergedCellsRef := mc.Ref
if !strings.Contains(mergedCellsRef, ":") {
mergedCellsRef += ":" + mergedCellsRef
}
mc.rect, err = rangeRefToCoordinates(mergedCellsRef)
}
return mc.rect, err
}
// MergeCell provides a function to merge cells by given range reference and
// sheet name. Merging cells only keeps the upper-left cell value, and
// discards the other values. For example create a merged cell of D3:E9 on
// Sheet1:
//
// err := f.MergeCell("Sheet1", "D3", "E9")
//
// If you create a merged cell that overlaps with another existing merged cell,
// those merged cells that already exist will be removed. The cell references
// tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1)
// A8(x3,y4) D8(x2,y4)
//
// B1(x1,y1) D1(x2,y1)
// +------------------------+
// | |
// A4(x3,y3) | C4(x4,y3) |
// +------------------------+ |
// | | | |
// | |B5(x1,y2) | D5(x2,y2)|
// | +------------------------+
// | |
// |A8(x3,y4) C8(x4,y4)|
// +------------------------+
func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error {
rect, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell)
if err != nil {
return err
}
// Correct the range reference, such correct C1:B3 to B1:C3.
_ = sortCoordinates(rect)
topLeftCell, _ = CoordinatesToCellName(rect[0], rect[1])
bottomRightCell, _ = CoordinatesToCellName(rect[2], rect[3])
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock()
defer ws.mu.Unlock()
for col := rect[0]; col <= rect[2]; col++ {
for row := rect[1]; row <= rect[3]; row++ {
if col == rect[0] && row == rect[1] {
continue
}
ws.prepareSheetXML(col, row)
c := &ws.SheetData.Row[row-1].C[col-1]
c.setCellDefault("")
_ = f.removeFormula(c, ws, sheet)
}
}
ref := topLeftCell + ":" + bottomRightCell
if ws.MergeCells != nil {
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect})
} else {
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
}
ws.MergeCells.Count = len(ws.MergeCells.Cells)
return err
}
// UnmergeCell provides a function to unmerge a given range reference.
// For example unmerge range reference D3:E9 on Sheet1:
//
// err := f.UnmergeCell("Sheet1", "D3", "E9")
//
// Attention: overlapped range will also be unmerged.
func (f *File) UnmergeCell(sheet, topLeftCell, bottomRightCell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock()
defer ws.mu.Unlock()
rect1, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell)
if err != nil {
return err
}
// Correct the range reference, such correct C1:B3 to B1:C3.
_ = sortCoordinates(rect1)
// return nil since no MergeCells in the sheet
if ws.MergeCells == nil {
return nil
}
if err = ws.mergeOverlapCells(); err != nil {
return err
}
f.clearCalcCache()
i := 0
for _, mergeCell := range ws.MergeCells.Cells {
if rect2, _ := rangeRefToCoordinates(mergeCell.Ref); isOverlap(rect1, rect2) {
continue
}
ws.MergeCells.Cells[i] = mergeCell
i++
}
ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
ws.MergeCells.Count = len(ws.MergeCells.Cells)
if ws.MergeCells.Count == 0 {
ws.MergeCells = nil
}
return nil
}
// GetMergeCells provides a function to get all merged cells from a specific
// worksheet. If the `withoutValues` parameter is set to true, it will not
// return the cell values of merged cells, only the range reference will be
// returned. For example get all merged cells on Sheet1:
//
// mergeCells, err := f.GetMergeCells("Sheet1")
//
// If you want to get merged cells without cell values, you can use the
// following code:
//
// mergeCells, err := f.GetMergeCells("Sheet1", true)
func (f *File) GetMergeCells(sheet string, withoutValues ...bool) ([]MergeCell, error) {
var (
mergeCells []MergeCell
withoutVal bool
)
if len(withoutValues) > 0 {
withoutVal = withoutValues[0]
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return mergeCells, err
}
if ws.MergeCells != nil {
if err = ws.mergeOverlapCells(); err != nil {
return mergeCells, err
}
mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
for i := range ws.MergeCells.Cells {
ref, val := ws.MergeCells.Cells[i].Ref, ""
if !withoutVal {
cell := strings.Split(ref, ":")[0]
val, _ = f.GetCellValue(sheet, cell)
}
mergeCells = append(mergeCells, []string{ref, val})
}
}
return mergeCells, err
}
// mergeOverlapCells merge overlap cells.
func (ws *xlsxWorksheet) mergeOverlapCells() error {
var (
err error
rectList [][]int
merged = true
)
for _, cell := range ws.MergeCells.Cells {
if cell == nil {
continue
}
if cell.rect, err = cell.Rect(); err != nil {
return err
}
rectList = append(rectList, cell.rect)
}
for merged {
merged = false
var mergedRectList [][]int
used := make([]bool, len(rectList))
for i := 0; i < len(rectList); i++ {
if used[i] {
continue
}
r1 := rectList[i]
for j := i + 1; j < len(rectList); j++ {
if used[j] {
continue
}
if r2 := rectList[j]; isOverlap(r1, r2) {
r1 = []int{
min(r1[0], r2[0]),
min(r1[1], r2[1]),
max(r1[2], r2[2]),
max(r1[3], r2[3]),
}
merged, used[j] = true, true
}
}
mergedRectList = append(mergedRectList, r1)
}
rectList = mergedRectList
}
ws.MergeCells.Cells = make([]*xlsxMergeCell, 0, len(rectList))
for _, r := range rectList {
ref, _ := coordinatesToRangeRef(r)
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: r})
}
ws.MergeCells.Count = len(ws.MergeCells.Cells)
return err
}
// MergeCell define a merged cell data.
// It consists of the following structure.
// example: []string{"D4:E10", "cell value"}
type MergeCell []string
// GetCellValue returns merged cell value.
func (m *MergeCell) GetCellValue() string {
return (*m)[1]
}
// GetStartAxis returns the top left cell reference of merged range, for
// example: "C2".
func (m *MergeCell) GetStartAxis() string {
return strings.Split((*m)[0], ":")[0]
}
// GetEndAxis returns the bottom right cell reference of merged range, for
// example: "D4".
func (m *MergeCell) GetEndAxis() string {
return strings.Split((*m)[0], ":")[1]
}
================================================
FILE: merge_test.go
================================================
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMergeCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.EqualError(t, f.MergeCell("Sheet1", "A", "B"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
for _, cells := range [][]string{
{"D9", "D9"},
{"D9", "E9"},
{"H14", "G13"},
{"C9", "D8"},
{"F11", "G13"},
{"H7", "B15"},
{"D11", "F13"},
{"G10", "K12"},
} {
assert.NoError(t, f.MergeCell("Sheet1", cells[0], cells[1]))
}
assert.NoError(t, f.SetCellValue("Sheet1", "G11", "set value in merged cell"))
assert.NoError(t, f.SetCellInt("Sheet1", "H11", 100))
assert.NoError(t, f.SetCellValue("Sheet1", "I11", 0.5))
assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)"))
value, err := f.GetCellValue("Sheet1", "H11")
assert.Equal(t, "100", value)
assert.NoError(t, err)
// Merged cell ref is single coordinate
value, err = f.GetCellValue("Sheet2", "A6")
assert.Empty(t, value)
assert.NoError(t, err)
value, err = f.GetCellFormula("Sheet1", "G12")
assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value)
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
for _, cells := range [][]string{
{"D11", "F13"},
{"G10", "K12"},
{"B1", "D5"}, // B1:D5
{"E1", "F5"}, // E1:F5
{"H2", "I5"},
{"I4", "J6"}, // H2:J6
{"M2", "N5"},
{"L4", "M6"}, // L2:N6
{"P4", "Q7"},
{"O2", "P5"}, // O2:Q7
{"A9", "B12"},
{"B7", "C9"}, // A7:C12
{"E9", "F10"},
{"D8", "G12"},
{"I8", "I12"},
{"I10", "K10"},
{"M8", "Q13"},
{"N10", "O11"},
} {
assert.NoError(t, f.MergeCell("Sheet3", cells[0], cells[1]))
}
// Test merge cells on not exists worksheet
assert.EqualError(t, f.MergeCell("SheetN", "N10", "O11"), "sheet SheetN does not exist")
// Test merged cells with invalid sheet name
assert.EqualError(t, f.MergeCell("Sheet:1", "N10", "O11"), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCell.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
// Test getting merged cells with the same start and end axis
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
mergedCells, err := f.GetMergeCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "A1", mergedCells[0].GetStartAxis())
assert.Equal(t, "A1", mergedCells[0].GetEndAxis())
assert.Empty(t, mergedCells[0].GetCellValue())
}
func TestMergeCellOverlap(t *testing.T) {
f := NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A1", "C2"))
assert.NoError(t, f.MergeCell("Sheet1", "B2", "D3"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCellOverlap.xlsx")))
f, err := OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
mc, err := f.GetMergeCells("Sheet1")
assert.NoError(t, err)
assert.Len(t, mc, 1)
assert.Equal(t, "A1", mc[0].GetStartAxis())
assert.Equal(t, "D3", mc[0].GetEndAxis())
assert.Empty(t, mc[0].GetCellValue())
assert.NoError(t, f.Close())
}
func TestGetMergeCells(t *testing.T) {
wants := []struct {
value string
start string
end string
}{{
value: "A1",
start: "A1",
end: "B1",
}, {
value: "A2",
start: "A2",
end: "A3",
}, {
value: "A4",
start: "A4",
end: "B5",
}, {
value: "A7",
start: "A7",
end: "C10",
}}
f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
sheet1 := f.GetSheetName(0)
mergeCells, err := f.GetMergeCells(sheet1)
assert.NoError(t, err)
assert.Len(t, mergeCells, len(wants))
for i, m := range mergeCells {
assert.Equal(t, wants[i].value, m.GetCellValue())
assert.Equal(t, wants[i].start, m.GetStartAxis())
assert.Equal(t, wants[i].end, m.GetEndAxis())
}
// Test get merged cells without cell values
mergeCells, err = f.GetMergeCells(sheet1, true)
assert.NoError(t, err)
assert.Len(t, mergeCells, len(wants))
for i, m := range mergeCells {
assert.Empty(t, m.GetCellValue())
assert.Equal(t, wants[i].start, m.GetStartAxis())
assert.Equal(t, wants[i].end, m.GetEndAxis())
}
// Test get merged cells with invalid sheet name
_, err = f.GetMergeCells("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test get merged cells on not exists worksheet
_, err = f.GetMergeCells("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
}
func TestUnmergeCell(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
sheet1 := f.GetSheetName(0)
sheet, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
mergeCellNum := len(sheet.MergeCells.Cells)
assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test unmerge the merged cells that contains A1
assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
if len(sheet.MergeCells.Cells) != mergeCellNum-1 {
t.FailNow()
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnmergeCell.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
// Test unmerged range reference on not exists worksheet
assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN does not exist")
// Test unmerge the merged cells with invalid sheet name
assert.EqualError(t, f.UnmergeCell("Sheet:1", "A1", "A1"), ErrSheetNameInvalid.Error())
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = nil
assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15"))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7"))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
assert.NoError(t, f.UnmergeCell("Sheet1", "A2", "B3"))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
func TestMergeCellsParser(t *testing.T) {
ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{nil}}}
_, err := ws.mergeCellsParser("A1")
assert.NoError(t, err)
}
================================================
FILE: numfmt.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"fmt"
"math"
"math/big"
"strconv"
"strings"
"time"
"github.com/xuri/nfp"
)
// languageInfo defined the required fields of localization support for number
// format.
type languageInfo struct {
apFmt string
tags, weekdayNames, weekdayNamesAbbr []string
useGannen bool
localMonth func(t time.Time, abbr int) string
}
// numberFormat directly maps the number format parser runtime required
// fields.
type numberFormat struct {
opts *Options
cellType CellType
section []nfp.Section
t time.Time
sectionIdx int
date1904, isNumeric, hours, seconds, useMillisecond, useGannen bool
number float64
ap, localCode, result, value, valueSectionType string
switchArgument, currencyString string
fracHolder, fracPadding, intHolder, intPadding, expBaseLen int
percent int
useCommaSep, useFraction, usePointer, usePositive, useScientificNotation bool
}
// CultureName is the type of supported language country codes types for apply
// number format.
type CultureName byte
// This section defines the currently supported country code types enumeration
// for apply number format.
const (
CultureNameUnknown CultureName = iota
CultureNameEnUS
CultureNameJaJP
CultureNameKoKR
CultureNameZhCN
CultureNameZhTW
)
var (
// Excel styles can reference number formats that are built-in, all of which
// have an id less than 164. Note that this number format code list is under
// English localization.
builtInNumFmt = map[int]string{
0: "general",
1: "0",
2: "0.00",
3: "#,##0",
4: "#,##0.00",
9: "0%",
10: "0.00%",
11: "0.00E+00",
12: "# ?/?",
13: "# ??/??",
14: "mm-dd-yy",
15: "d-mmm-yy",
16: "d-mmm",
17: "mmm-yy",
18: "h:mm AM/PM",
19: "h:mm:ss AM/PM",
20: "hh:mm",
21: "hh:mm:ss",
22: "m/d/yy hh:mm",
37: "#,##0 ;(#,##0)",
38: "#,##0 ;[red](#,##0)",
39: "#,##0.00 ;(#,##0.00)",
40: "#,##0.00 ;[red](#,##0.00)",
41: "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)",
42: "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)",
43: "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)",
44: "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)",
45: "mm:ss",
46: "[h]:mm:ss",
47: "mm:ss.0",
48: "##0.0E+0",
49: "@",
}
// langNumFmt defined number format code provided for language glyphs where
// they occur in different language.
langNumFmt = map[string]map[int]string{
"zh-tw": {
27: "[$-404]e/m/d",
28: "[$-404]e\"年\"m\"月\"d\"日\"",
29: "[$-404]e\"年\"m\"月\"d\"日\"",
30: "m/d/yy",
31: "yyyy\"年\"m\"月\"d\"日\"",
32: "hh\"時\"mm\"分\"",
33: "hh\"時\"mm\"分\"ss\"秒\"",
34: "上午/下午hh\"時\"mm\"分\"",
35: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
36: "[$-404]e/m/d",
50: "[$-404]e/m/d",
51: "[$-404]e\"年\"m\"月\"d\"日\"",
52: "上午/下午hh\"時\"mm\"分\"",
53: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
54: "[$-404]e\"年\"m\"月\"d\"日\"",
55: "上午/下午hh\"時\"mm\"分\"",
56: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
57: "[$-404]e/m/d",
58: "[$-404]e\"年\"m\"月\"d\"日\"",
},
"zh-cn": {
27: "yyyy\"年\"m\"月\"",
28: "m\"月\"d\"日\"",
29: "m\"月\"d\"日\"",
30: "m/d/yy",
31: "yyyy\"年\"m\"月\"d\"日\"",
32: "h\"时\"mm\"分\"",
33: "h\"时\"mm\"分\"ss\"秒\"",
34: "上午/下午h\"时\"mm\"分\"",
35: "上午/下午h\"时\"mm\"分\"ss\"秒\"",
36: "yyyy\"年\"m\"月\"",
50: "yyyy\"年\"m\"月\"",
51: "m\"月\"d\"日\"",
52: "yyyy\"年\"m\"月\"",
53: "m\"月\"d\"日\"",
54: "m\"月\"d\"日\"",
55: "上午/下午h\"时\"mm\"分\"",
56: "上午/下午h\"时\"mm\"分\"ss\"秒\"",
57: "yyyy\"年\"m\"月\"",
58: "m\"月\"d\"日\"",
},
"ja-jp": {
27: "[$-411]ge.m.d",
28: "[$-411]ggge\"年\"m\"月\"d\"日\"",
29: "[$-411]ggge\"年\"m\"月\"d\"日\"",
30: "m/d/yy",
31: "yyyy\"年\"m\"月\"d\"日\"",
32: "h\"時\"mm\"分\"",
33: "h\"時\"mm\"分\"ss\"秒\"",
34: "yyyy\"年\"m\"月\"",
35: "m\"月\"d\"日\"",
36: "[$-411]ge.m.d",
50: "[$-411]ge.m.d",
51: "[$-411]ggge\"年\"m\"月\"d\"日\"",
52: "yyyy\"年\"m\"月\"",
53: "m\"月\"d\"日\"",
54: "[$-411]ggge\"年\"m\"月\"d\"日\"",
55: "yyyy\"年\"m\"月\"",
56: "m\"月\"d\"日\"",
57: "[$-411]ge.m.d",
58: "[$-411]ggge\"年\"m\"月\"d\"日\"",
},
"ko-kr": {
27: "yyyy\"年\" mm\"月\" dd\"日\"",
28: "mm-dd",
29: "mm-dd",
30: "mm-dd-yy",
31: "yyyy\"년\" mm\"월\" dd\"일\"",
32: "h\"시\" mm\"분\"",
33: "h\"시\" mm\"분\" ss\"초\"",
34: "yyyy-mm-dd",
35: "yyyy-mm-dd",
36: "yyyy\"年\" mm\"月\" dd\"日\"",
50: "yyyy\"年\" mm\"月\" dd\"日\"",
51: "mm-dd",
52: "yyyy-mm-dd",
53: "yyyy-mm-dd",
54: "mm-dd",
55: "yyyy-mm-dd",
56: "yyyy-mm-dd",
57: "yyyy\"年\" mm\"月\" dd\"日\"",
58: "mm-dd",
},
"th-th": {
59: "t0",
60: "t0.00",
61: "t#,##0",
62: "t#,##0.00",
67: "t0%",
68: "t0.00%",
69: "t# ?/?",
70: "t# ??/??",
71: "\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B",
72: "\u0E27-\u0E14\u0E14\u0E14-\u0E1B\u0E1B",
73: "\u0E27-\u0E14\u0E14\u0E14",
74: "\u0E14\u0E14\u0E14-\u0E1B\u0E1B",
75: "\u0E0A:\u0E19\u0E19",
76: "\u0E0A:\u0E19\u0E19:\u0E17\u0E17",
77: "\u0E27/\u0E14/\u0E1B\u0E1B\u0E1B\u0E1B \u0E0A:\u0E19\u0E19",
78: "\u0E19\u0E19:\u0E17\u0E17",
79: "[\u0E0A]:\u0E19\u0E19:\u0E17\u0E17",
80: "\u0E19\u0E19:\u0E17\u0E17.0",
81: "d/m/bb",
},
}
// currencyNumFmt defined the currency number format map.
currencyNumFmt = map[int]string{
164: "\"¥\"#,##0.00",
165: "[$$-409]#,##0.00",
166: "[$$-45C]#,##0.00",
167: "[$$-1004]#,##0.00",
168: "[$$-404]#,##0.00",
169: "[$$-C09]#,##0.00",
170: "[$$-2809]#,##0.00",
171: "[$$-1009]#,##0.00",
172: "[$$-2009]#,##0.00",
173: "[$$-1409]#,##0.00",
174: "[$$-4809]#,##0.00",
175: "[$$-2C09]#,##0.00",
176: "[$$-2409]#,##0.00",
177: "[$$-1000]#,##0.00",
178: "#,##0.00\\ [$$-C0C]",
179: "[$$-475]#,##0.00",
180: "[$$-83E]#,##0.00",
181: "[$$-86B]\\ #,##0.00",
182: "[$$-340A]\\ #,##0.00",
183: "[$$-240A]#,##0.00",
184: "[$$-300A]\\ #,##0.00",
185: "[$$-440A]#,##0.00",
186: "[$$-80A]#,##0.00",
187: "[$$-500A]#,##0.00",
188: "[$$-540A]#,##0.00",
189: "[$$-380A]\\ #,##0.00",
190: "[$£-809]#,##0.00",
191: "[$£-491]#,##0.00",
192: "[$£-452]#,##0.00",
193: "[$¥-804]#,##0.00",
194: "[$¥-411]#,##0.00",
195: "[$¥-478]#,##0.00",
196: "[$¥-451]#,##0.00",
197: "[$¥-480]#,##0.00",
198: "#,##0.00\\ [$\u058F-42B]",
199: "[$\u060B-463]#,##0.00",
200: "[$\u060B-48C]#,##0.00",
201: "[$\u09F3-845]\\ #,##0.00",
202: "#,##0.00[$\u17DB-453]",
203: "[$\u20A1-140A]#,##0.00",
204: "[$\u20A6-468]\\ #,##0.00",
205: "[$\u20A6-470]\\ #,##0.00",
206: "[$\u20A9-412]#,##0.00",
207: "[$\u20AA-40D]\\ #,##0.00",
208: "#,##0.00\\ [$\u20AB-42A]",
209: "#,##0.00\\ [$\u20AC-42D]",
210: "#,##0.00\\ [$\u20AC-47E]",
211: "#,##0.00\\ [$\u20AC-403]",
212: "#,##0.00\\ [$\u20AC-483]",
213: "[$\u20AC-813]\\ #,##0.00",
214: "[$\u20AC-413]\\ #,##0.00",
215: "[$\u20AC-1809]#,##0.00",
216: "#,##0.00\\ [$\u20AC-425]",
217: "[$\u20AC-2]\\ #,##0.00",
218: "#,##0.00\\ [$\u20AC-1]",
219: "#,##0.00\\ [$\u20AC-40B]",
220: "#,##0.00\\ [$\u20AC-80C]",
221: "#,##0.00\\ [$\u20AC-40C]",
222: "#,##0.00\\ [$\u20AC-140C]",
223: "#,##0.00\\ [$\u20AC-180C]",
224: "[$\u20AC-200C]#,##0.00",
225: "#,##0.00\\ [$\u20AC-456]",
226: "#,##0.00\\ [$\u20AC-C07]",
227: "#,##0.00\\ [$\u20AC-407]",
228: "#,##0.00\\ [$\u20AC-1007]",
229: "#,##0.00\\ [$\u20AC-408]",
230: "#,##0.00\\ [$\u20AC-243B]",
231: "[$\u20AC-83C]#,##0.00",
232: "[$\u20AC-410]\\ #,##0.00",
233: "[$\u20AC-476]#,##0.00",
234: "#,##0.00\\ [$\u20AC-2C1A]",
235: "[$\u20AC-426]\\ #,##0.00",
236: "#,##0.00\\ [$\u20AC-427]",
237: "#,##0.00\\ [$\u20AC-82E]",
238: "#,##0.00\\ [$\u20AC-46E]",
239: "[$\u20AC-43A]#,##0.00",
240: "#,##0.00\\ [$\u20AC-C3B]",
241: "#,##0.00\\ [$\u20AC-482]",
242: "#,##0.00\\ [$\u20AC-816]",
243: "#,##0.00\\ [$\u20AC-301A]",
244: "#,##0.00\\ [$\u20AC-203B]",
245: "#,##0.00\\ [$\u20AC-41B]",
246: "#,##0.00\\ [$\u20AC-424]",
247: "#,##0.00\\ [$\u20AC-C0A]",
248: "#,##0.00\\ [$\u20AC-81D]",
249: "#,##0.00\\ [$\u20AC-484]",
250: "#,##0.00\\ [$\u20AC-42E]",
251: "[$\u20AC-462]\\ #,##0.00",
252: "#,##0.00\\ [$₭-454]",
253: "#,##0.00\\ [$₮-450]",
254: "[$\u20AE-C50]#,##0.00",
255: "[$\u20B1-3409]#,##0.00",
256: "[$\u20B1-464]#,##0.00",
257: "#,##0.00[$\u20B4-422]",
258: "[$\u20B8-43F]#,##0.00",
259: "[$\u20B9-460]#,##0.00",
260: "[$\u20B9-4009]\\ #,##0.00",
261: "[$\u20B9-447]\\ #,##0.00",
262: "[$\u20B9-439]\\ #,##0.00",
263: "[$\u20B9-44B]\\ #,##0.00",
264: "[$\u20B9-860]#,##0.00",
265: "[$\u20B9-457]\\ #,##0.00",
266: "[$\u20B9-458]#,##0.00",
267: "[$\u20B9-44E]\\ #,##0.00",
268: "[$\u20B9-861]#,##0.00",
269: "[$\u20B9-448]\\ #,##0.00",
270: "[$\u20B9-446]\\ #,##0.00",
271: "[$\u20B9-44F]\\ #,##0.00",
272: "[$\u20B9-459]#,##0.00",
273: "[$\u20B9-449]\\ #,##0.00",
274: "[$\u20B9-820]#,##0.00",
275: "#,##0.00\\ [$\u20BA-41F]",
276: "#,##0.00\\ [$\u20BC-42C]",
277: "#,##0.00\\ [$\u20BC-82C]",
278: "#,##0.00\\ [$\u20BD-419]",
279: "#,##0.00[$\u20BD-485]",
280: "#,##0.00\\ [$\u20BE-437]",
281: "[$B/.-180A]\\ #,##0.00",
282: "[$Br-472]#,##0.00",
283: "[$Br-477]#,##0.00",
284: "#,##0.00[$Br-473]",
285: "[$Bs-46B]\\ #,##0.00",
286: "[$Bs-400A]\\ #,##0.00",
287: "[$Bs.-200A]\\ #,##0.00",
288: "[$BWP-832]\\ #,##0.00",
289: "[$C$-4C0A]#,##0.00",
290: "[$CA$-85D]#,##0.00",
291: "[$CA$-47C]#,##0.00",
292: "[$CA$-45D]#,##0.00",
293: "[$CFA-340C]#,##0.00",
294: "[$CFA-280C]#,##0.00",
295: "#,##0.00\\ [$CFA-867]",
296: "#,##0.00\\ [$CFA-488]",
297: "#,##0.00\\ [$CHF-100C]",
298: "[$CHF-1407]\\ #,##0.00",
299: "[$CHF-807]\\ #,##0.00",
300: "[$CHF-810]\\ #,##0.00",
301: "[$CHF-417]\\ #,##0.00",
302: "[$CLP-47A]\\ #,##0.00",
303: "[$CN¥-850]#,##0.00",
304: "#,##0.00\\ [$DZD-85F]",
305: "[$FCFA-2C0C]#,##0.00",
306: "#,##0.00\\ [$Ft-40E]",
307: "[$G-3C0C]#,##0.00",
308: "[$Gs.-3C0A]\\ #,##0.00",
309: "[$GTQ-486]#,##0.00",
310: "[$HK$-C04]#,##0.00",
311: "[$HK$-3C09]#,##0.00",
312: "#,##0.00\\ [$HRK-41A]",
313: "[$IDR-3809]#,##0.00",
314: "[$IQD-492]#,##0.00",
315: "#,##0.00\\ [$ISK-40F]",
316: "[$K-455]#,##0.00",
317: "#,##0.00\\ [$K\u010D-405]",
318: "#,##0.00\\ [$KM-141A]",
319: "#,##0.00\\ [$KM-101A]",
320: "#,##0.00\\ [$KM-181A]",
321: "[$kr-438]\\ #,##0.00",
322: "[$kr-43B]\\ #,##0.00",
323: "#,##0.00\\ [$kr-83B]",
324: "[$kr-414]\\ #,##0.00",
325: "[$kr-814]\\ #,##0.00",
326: "#,##0.00\\ [$kr-41D]",
327: "[$kr.-406]\\ #,##0.00",
328: "[$kr.-46F]\\ #,##0.00",
329: "[$Ksh-441]#,##0.00",
330: "[$L-818]#,##0.00",
331: "[$L-819]#,##0.00",
332: "[$L-480A]\\ #,##0.00",
333: "#,##0.00\\ [$Lek\u00EB-41C]",
334: "[$MAD-45F]#,##0.00",
335: "[$MAD-380C]#,##0.00",
336: "#,##0.00\\ [$MAD-105F]",
337: "[$MOP$-1404]#,##0.00",
338: "#,##0.00\\ [$MVR-465]_-",
339: "#,##0.00[$Nfk-873]",
340: "[$NGN-466]#,##0.00",
341: "[$NGN-467]#,##0.00",
342: "[$NGN-469]#,##0.00",
343: "[$NGN-471]#,##0.00",
344: "[$NOK-103B]\\ #,##0.00",
345: "[$NOK-183B]\\ #,##0.00",
346: "[$NZ$-481]#,##0.00",
347: "[$PKR-859]\\ #,##0.00",
348: "[$PYG-474]#,##0.00",
349: "[$Q-100A]#,##0.00",
350: "[$R-436]\\ #,##0.00",
351: "[$R-1C09]\\ #,##0.00",
352: "[$R-435]\\ #,##0.00",
353: "[$R$-416]\\ #,##0.00",
354: "[$RD$-1C0A]#,##0.00",
355: "#,##0.00\\ [$RF-487]",
356: "[$RM-4409]#,##0.00",
357: "[$RM-43E]#,##0.00",
358: "#,##0.00\\ [$RON-418]",
359: "[$Rp-421]#,##0.00",
360: "[$Rs-420]#,##0.00_-",
361: "[$Rs.-849]\\ #,##0.00",
362: "#,##0.00\\ [$RSD-81A]",
363: "#,##0.00\\ [$RSD-C1A]",
364: "#,##0.00\\ [$RUB-46D]",
365: "#,##0.00\\ [$RUB-444]",
366: "[$S/.-C6B]\\ #,##0.00",
367: "[$S/.-280A]\\ #,##0.00",
368: "#,##0.00\\ [$SEK-143B]",
369: "#,##0.00\\ [$SEK-1C3B]",
370: "#,##0.00\\ [$so\u02BBm-443]",
371: "#,##0.00\\ [$so\u02BBm-843]",
372: "#,##0.00\\ [$SYP-45A]",
373: "[$THB-41E]#,##0.00",
374: "#,##0.00[$TMT-442]",
375: "[$US$-3009]#,##0.00",
376: "[$ZAR-46C]\\ #,##0.00",
377: "[$ZAR-430]#,##0.00",
378: "[$ZAR-431]#,##0.00",
379: "[$ZAR-432]\\ #,##0.00",
380: "[$ZAR-433]#,##0.00",
381: "[$ZAR-434]\\ #,##0.00",
382: "#,##0.00\\ [$z\u0142-415]",
383: "#,##0.00\\ [$\u0434\u0435\u043D-42F]",
384: "#,##0.00\\ [$КМ-201A]",
385: "#,##0.00\\ [$КМ-1C1A]",
386: "#,##0.00\\ [$\u043B\u0432.-402]",
387: "#,##0.00\\ [$р.-423]",
388: "#,##0.00\\ [$\u0441\u043E\u043C-440]",
389: "#,##0.00\\ [$\u0441\u043E\u043C-428]",
390: "[$\u062C.\u0645.-C01]\\ #,##0.00_-",
391: "[$\u062F.\u0623.-2C01]\\ #,##0.00_-",
392: "[$\u062F.\u0625.-3801]\\ #,##0.00_-",
393: "[$\u062F.\u0628.-3C01]\\ #,##0.00_-",
394: "[$\u062F.\u062A.-1C01]\\ #,##0.00_-",
395: "[$\u062F.\u062C.-1401]\\ #,##0.00_-",
396: "[$\u062F.\u0639.-801]\\ #,##0.00_-",
397: "[$\u062F.\u0643.-3401]\\ #,##0.00_-",
398: "[$\u062F.\u0644.-1001]#,##0.00_-",
399: "[$\u062F.\u0645.-1801]\\ #,##0.00_-",
400: "[$\u0631-846]\\ #,##0.00",
401: "[$\u0631.\u0633.-401]\\ #,##0.00_-",
402: "[$\u0631.\u0639.-2001]\\ #,##0.00_-",
403: "[$\u0631.\u0642.-4001]\\ #,##0.00_-",
404: "[$\u0631.\u064A.-2401]\\ #,##0.00_-",
405: "[$\u0631\u06CC\u0627\u0644-429]#,##0.00_-",
406: "[$\u0644.\u0633.-2801]\\ #,##0.00_-",
407: "[$\u0644.\u0644.-3001]\\ #,##0.00_-",
408: "[$\u1265\u122D-45E]#,##0.00",
409: "[$\u0930\u0942-461]#,##0.00",
410: "[$\u0DBB\u0DD4.-45B]\\ #,##0.00",
411: "[$ADP]\\ #,##0.00",
412: "[$AED]\\ #,##0.00",
413: "[$AFA]\\ #,##0.00",
414: "[$AFN]\\ #,##0.00",
415: "[$ALL]\\ #,##0.00",
416: "[$AMD]\\ #,##0.00",
417: "[$ANG]\\ #,##0.00",
418: "[$AOA]\\ #,##0.00",
419: "[$ARS]\\ #,##0.00",
420: "[$ATS]\\ #,##0.00",
421: "[$AUD]\\ #,##0.00",
422: "[$AWG]\\ #,##0.00",
423: "[$AZM]\\ #,##0.00",
424: "[$AZN]\\ #,##0.00",
425: "[$BAM]\\ #,##0.00",
426: "[$BBD]\\ #,##0.00",
427: "[$BDT]\\ #,##0.00",
428: "[$BEF]\\ #,##0.00",
429: "[$BGL]\\ #,##0.00",
430: "[$BGN]\\ #,##0.00",
431: "[$BHD]\\ #,##0.00",
432: "[$BIF]\\ #,##0.00",
433: "[$BMD]\\ #,##0.00",
434: "[$BND]\\ #,##0.00",
435: "[$BOB]\\ #,##0.00",
436: "[$BOV]\\ #,##0.00",
437: "[$BRL]\\ #,##0.00",
438: "[$BSD]\\ #,##0.00",
439: "[$BTN]\\ #,##0.00",
440: "[$BWP]\\ #,##0.00",
441: "[$BYR]\\ #,##0.00",
442: "[$BZD]\\ #,##0.00",
443: "[$CAD]\\ #,##0.00",
444: "[$CDF]\\ #,##0.00",
445: "[$CHE]\\ #,##0.00",
446: "[$CHF]\\ #,##0.00",
447: "[$CHW]\\ #,##0.00",
448: "[$CLF]\\ #,##0.00",
449: "[$CLP]\\ #,##0.00",
450: "[$CNY]\\ #,##0.00",
451: "[$COP]\\ #,##0.00",
452: "[$COU]\\ #,##0.00",
453: "[$CRC]\\ #,##0.00",
454: "[$CSD]\\ #,##0.00",
455: "[$CUC]\\ #,##0.00",
456: "[$CVE]\\ #,##0.00",
457: "[$CYP]\\ #,##0.00",
458: "[$CZK]\\ #,##0.00",
459: "[$DEM]\\ #,##0.00",
460: "[$DJF]\\ #,##0.00",
461: "[$DKK]\\ #,##0.00",
462: "[$DOP]\\ #,##0.00",
463: "[$DZD]\\ #,##0.00",
464: "[$ECS]\\ #,##0.00",
465: "[$ECV]\\ #,##0.00",
466: "[$EEK]\\ #,##0.00",
467: "[$EGP]\\ #,##0.00",
468: "[$ERN]\\ #,##0.00",
469: "[$ESP]\\ #,##0.00",
470: "[$ETB]\\ #,##0.00",
471: "[$EUR]\\ #,##0.00",
472: "[$FIM]\\ #,##0.00",
473: "[$FJD]\\ #,##0.00",
474: "[$FKP]\\ #,##0.00",
475: "[$FRF]\\ #,##0.00",
476: "[$GBP]\\ #,##0.00",
477: "[$GEL]\\ #,##0.00",
478: "[$GHC]\\ #,##0.00",
479: "[$GHS]\\ #,##0.00",
480: "[$GIP]\\ #,##0.00",
481: "[$GMD]\\ #,##0.00",
482: "[$GNF]\\ #,##0.00",
483: "[$GRD]\\ #,##0.00",
484: "[$GTQ]\\ #,##0.00",
485: "[$GYD]\\ #,##0.00",
486: "[$HKD]\\ #,##0.00",
487: "[$HNL]\\ #,##0.00",
488: "[$HRK]\\ #,##0.00",
489: "[$HTG]\\ #,##0.00",
490: "[$HUF]\\ #,##0.00",
491: "[$IDR]\\ #,##0.00",
492: "[$IEP]\\ #,##0.00",
493: "[$ILS]\\ #,##0.00",
494: "[$INR]\\ #,##0.00",
495: "[$IQD]\\ #,##0.00",
496: "[$IRR]\\ #,##0.00",
497: "[$ISK]\\ #,##0.00",
498: "[$ITL]\\ #,##0.00",
499: "[$JMD]\\ #,##0.00",
500: "[$JOD]\\ #,##0.00",
501: "[$JPY]\\ #,##0.00",
502: "[$KAF]\\ #,##0.00",
503: "[$KES]\\ #,##0.00",
504: "[$KGS]\\ #,##0.00",
505: "[$KHR]\\ #,##0.00",
506: "[$KMF]\\ #,##0.00",
507: "[$KPW]\\ #,##0.00",
508: "[$KRW]\\ #,##0.00",
509: "[$KWD]\\ #,##0.00",
510: "[$KYD]\\ #,##0.00",
511: "[$KZT]\\ #,##0.00",
512: "[$LAK]\\ #,##0.00",
513: "[$LBP]\\ #,##0.00",
514: "[$LKR]\\ #,##0.00",
515: "[$LRD]\\ #,##0.00",
516: "[$LSL]\\ #,##0.00",
517: "[$LTL]\\ #,##0.00",
518: "[$LUF]\\ #,##0.00",
519: "[$LVL]\\ #,##0.00",
520: "[$LYD]\\ #,##0.00",
521: "[$MAD]\\ #,##0.00",
522: "[$MDL]\\ #,##0.00",
523: "[$MGA]\\ #,##0.00",
524: "[$MGF]\\ #,##0.00",
525: "[$MKD]\\ #,##0.00",
526: "[$MMK]\\ #,##0.00",
527: "[$MNT]\\ #,##0.00",
528: "[$MOP]\\ #,##0.00",
529: "[$MRO]\\ #,##0.00",
530: "[$MTL]\\ #,##0.00",
531: "[$MUR]\\ #,##0.00",
532: "[$MVR]\\ #,##0.00",
533: "[$MWK]\\ #,##0.00",
534: "[$MXN]\\ #,##0.00",
535: "[$MXV]\\ #,##0.00",
536: "[$MYR]\\ #,##0.00",
537: "[$MZM]\\ #,##0.00",
538: "[$MZN]\\ #,##0.00",
539: "[$NAD]\\ #,##0.00",
540: "[$NGN]\\ #,##0.00",
541: "[$NIO]\\ #,##0.00",
542: "[$NLG]\\ #,##0.00",
543: "[$NOK]\\ #,##0.00",
544: "[$NPR]\\ #,##0.00",
545: "[$NTD]\\ #,##0.00",
546: "[$NZD]\\ #,##0.00",
547: "[$OMR]\\ #,##0.00",
548: "[$PAB]\\ #,##0.00",
549: "[$PEN]\\ #,##0.00",
550: "[$PGK]\\ #,##0.00",
551: "[$PHP]\\ #,##0.00",
552: "[$PKR]\\ #,##0.00",
553: "[$PLN]\\ #,##0.00",
554: "[$PTE]\\ #,##0.00",
555: "[$PYG]\\ #,##0.00",
556: "[$QAR]\\ #,##0.00",
557: "[$ROL]\\ #,##0.00",
558: "[$RON]\\ #,##0.00",
559: "[$RSD]\\ #,##0.00",
560: "[$RUB]\\ #,##0.00",
561: "[$RUR]\\ #,##0.00",
562: "[$RWF]\\ #,##0.00",
563: "[$SAR]\\ #,##0.00",
564: "[$SBD]\\ #,##0.00",
565: "[$SCR]\\ #,##0.00",
566: "[$SDD]\\ #,##0.00",
567: "[$SDG]\\ #,##0.00",
568: "[$SDP]\\ #,##0.00",
569: "[$SEK]\\ #,##0.00",
570: "[$SGD]\\ #,##0.00",
571: "[$SHP]\\ #,##0.00",
572: "[$SIT]\\ #,##0.00",
573: "[$SKK]\\ #,##0.00",
574: "[$SLL]\\ #,##0.00",
575: "[$SOS]\\ #,##0.00",
576: "[$SPL]\\ #,##0.00",
577: "[$SRD]\\ #,##0.00",
578: "[$SRG]\\ #,##0.00",
579: "[$STD]\\ #,##0.00",
580: "[$SVC]\\ #,##0.00",
581: "[$SYP]\\ #,##0.00",
582: "[$SZL]\\ #,##0.00",
583: "[$THB]\\ #,##0.00",
584: "[$TJR]\\ #,##0.00",
585: "[$TJS]\\ #,##0.00",
586: "[$TMM]\\ #,##0.00",
587: "[$TMT]\\ #,##0.00",
588: "[$TND]\\ #,##0.00",
589: "[$TOP]\\ #,##0.00",
590: "[$TRL]\\ #,##0.00",
591: "[$TRY]\\ #,##0.00",
592: "[$TTD]\\ #,##0.00",
593: "[$TWD]\\ #,##0.00",
594: "[$TZS]\\ #,##0.00",
595: "[$UAH]\\ #,##0.00",
596: "[$UGX]\\ #,##0.00",
597: "[$USD]\\ #,##0.00",
598: "[$USN]\\ #,##0.00",
599: "[$USS]\\ #,##0.00",
600: "[$UYI]\\ #,##0.00",
601: "[$UYU]\\ #,##0.00",
602: "[$UZS]\\ #,##0.00",
603: "[$VEB]\\ #,##0.00",
604: "[$VEF]\\ #,##0.00",
605: "[$VND]\\ #,##0.00",
606: "[$VUV]\\ #,##0.00",
607: "[$WST]\\ #,##0.00",
608: "[$XAF]\\ #,##0.00",
609: "[$XAG]\\ #,##0.00",
610: "[$XAU]\\ #,##0.00",
611: "[$XB5]\\ #,##0.00",
612: "[$XBA]\\ #,##0.00",
613: "[$XBB]\\ #,##0.00",
614: "[$XBC]\\ #,##0.00",
615: "[$XBD]\\ #,##0.00",
616: "[$XCD]\\ #,##0.00",
617: "[$XDR]\\ #,##0.00",
618: "[$XFO]\\ #,##0.00",
619: "[$XFU]\\ #,##0.00",
620: "[$XOF]\\ #,##0.00",
621: "[$XPD]\\ #,##0.00",
622: "[$XPF]\\ #,##0.00",
623: "[$XPT]\\ #,##0.00",
624: "[$XTS]\\ #,##0.00",
625: "[$XXX]\\ #,##0.00",
626: "[$YER]\\ #,##0.00",
627: "[$YUM]\\ #,##0.00",
628: "[$ZAR]\\ #,##0.00",
629: "[$ZMK]\\ #,##0.00",
630: "[$ZMW]\\ #,##0.00",
631: "[$ZWD]\\ #,##0.00",
632: "[$ZWL]\\ #,##0.00",
633: "[$ZWN]\\ #,##0.00",
634: "[$ZWR]\\ #,##0.00",
}
// supportedTokenTypes list the supported number format token types
// currently.
supportedTokenTypes = []string{
nfp.TokenTypeAlignment,
nfp.TokenSubTypeCurrencyString,
nfp.TokenSubTypeLanguageInfo,
nfp.TokenTypeColor,
nfp.TokenTypeCurrencyLanguage,
nfp.TokenTypeDateTimes,
nfp.TokenTypeDecimalPoint,
nfp.TokenTypeDenominator,
nfp.TokenTypeDigitalPlaceHolder,
nfp.TokenTypeElapsedDateTimes,
nfp.TokenTypeExponential,
nfp.TokenTypeFraction,
nfp.TokenTypeGeneral,
nfp.TokenTypeHashPlaceHolder,
nfp.TokenTypeLiteral,
nfp.TokenTypePercent,
nfp.TokenTypeRepeatsChar,
nfp.TokenTypeSwitchArgument,
nfp.TokenTypeTextPlaceHolder,
nfp.TokenTypeThousandsSeparator,
nfp.TokenTypeZeroPlaceHolder,
}
// supportedNumberTokenTypes list the supported number token types.
supportedNumberTokenTypes = []string{
nfp.TokenTypeDenominator,
nfp.TokenTypeDigitalPlaceHolder,
nfp.TokenTypeExponential,
nfp.TokenTypeFraction,
nfp.TokenTypeHashPlaceHolder,
nfp.TokenTypePercent,
nfp.TokenTypeZeroPlaceHolder,
}
// supportedDateTimeTokenTypes list the supported date and time token types.
supportedDateTimeTokenTypes = []string{
nfp.TokenTypeDateTimes,
nfp.TokenTypeElapsedDateTimes,
}
// supportedLanguageInfo directly maps the supported language decimal ID and
// tags.
supportedLanguageInfo = map[int]languageInfo{
54: {tags: []string{"af"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans, weekdayNames: weekdayNamesAfrikaans, weekdayNamesAbbr: weekdayNamesAfrikaansAbbr},
1078: {tags: []string{"af-ZA"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans, weekdayNames: weekdayNamesAfrikaans, weekdayNamesAbbr: weekdayNamesAfrikaansAbbr},
28: {tags: []string{"sq"}, localMonth: localMonthsNameAlbanian, apFmt: apFmtAlbanian, weekdayNames: weekdayNamesAlbanian, weekdayNamesAbbr: weekdayNamesAlbanianAbbr},
1052: {tags: []string{"sq-AL"}, localMonth: localMonthsNameAlbanian, apFmt: apFmtAlbanian, weekdayNames: weekdayNamesAlbanian, weekdayNamesAbbr: weekdayNamesAlbanianAbbr},
132: {tags: []string{"gsw"}, localMonth: localMonthsNameAlsatian, apFmt: apFmtAlsatian, weekdayNames: weekdayNamesAlsatian, weekdayNamesAbbr: weekdayNamesAlsatianAbbr},
1156: {tags: []string{"gsw-FR"}, localMonth: localMonthsNameAlsatianFrance, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAlsatianFrance, weekdayNamesAbbr: weekdayNamesAlsatianFranceAbbr},
94: {tags: []string{"am"}, localMonth: localMonthsNameAmharic, apFmt: apFmtAmharic, weekdayNames: weekdayNamesAmharic, weekdayNamesAbbr: weekdayNamesAmharicAbbr},
1118: {tags: []string{"am-ET"}, localMonth: localMonthsNameAmharic, apFmt: apFmtAmharic, weekdayNames: weekdayNamesAmharic, weekdayNamesAbbr: weekdayNamesAmharicAbbr},
1: {tags: []string{"ar"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
5121: {tags: []string{"ar-DZ"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
15361: {tags: []string{"ar-BH"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
3073: {tags: []string{"ar-EG"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
2049: {tags: []string{"ar-IQ"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
11265: {tags: []string{"ar-JO"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
13313: {tags: []string{"ar-KW"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
12289: {tags: []string{"ar-LB"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
4097: {tags: []string{"ar-LY"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
6145: {tags: []string{"ar-MA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
8193: {tags: []string{"ar-OM"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
16385: {tags: []string{"ar-QA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
1025: {tags: []string{"ar-SA"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
10241: {tags: []string{"ar-SY"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
7169: {tags: []string{"ar-TN"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
14337: {tags: []string{"ar-AE"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
9217: {tags: []string{"ar-YE"}, localMonth: localMonthsNameArabic, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
43: {tags: []string{"hy"}, localMonth: localMonthsNameArmenian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesArmenian, weekdayNamesAbbr: weekdayNamesArmenianAbbr},
1067: {tags: []string{"hy-AM"}, localMonth: localMonthsNameArmenian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesArmenian, weekdayNamesAbbr: weekdayNamesArmenianAbbr},
77: {tags: []string{"as"}, localMonth: localMonthsNameAssamese, apFmt: apFmtAssamese, weekdayNames: weekdayNamesAssamese, weekdayNamesAbbr: weekdayNamesAssameseAbbr},
1101: {tags: []string{"as-IN"}, localMonth: localMonthsNameAssamese, apFmt: apFmtAssamese, weekdayNames: weekdayNamesAssamese, weekdayNamesAbbr: weekdayNamesAssameseAbbr},
29740: {tags: []string{"az-Cyrl"}, localMonth: localMonthsNameAzerbaijaniCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijaniCyrillic, weekdayNamesAbbr: weekdayNamesAzerbaijaniCyrillicAbbr},
2092: {tags: []string{"az-Cyrl-AZ"}, localMonth: localMonthsNameAzerbaijaniCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijaniCyrillic, weekdayNamesAbbr: weekdayNamesAzerbaijaniCyrillicAbbr},
44: {tags: []string{"az"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr},
30764: {tags: []string{"az-Latn"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr},
1068: {tags: []string{"az-Latn-AZ"}, localMonth: localMonthsNameAzerbaijani, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesAzerbaijani, weekdayNamesAbbr: weekdayNamesAzerbaijaniAbbr},
69: {tags: []string{"bn"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr},
2117: {tags: []string{"bn-BD"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr},
1093: {tags: []string{"bn-IN"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBangla, weekdayNamesAbbr: weekdayNamesBanglaAbbr},
109: {tags: []string{"ba"}, localMonth: localMonthsNameBashkir, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBashkir, weekdayNamesAbbr: weekdayNamesBashkirAbbr},
1133: {tags: []string{"ba-RU"}, localMonth: localMonthsNameBashkir, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBashkir, weekdayNamesAbbr: weekdayNamesBashkirAbbr},
45: {tags: []string{"eu"}, localMonth: localMonthsNameBasque, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBasque, weekdayNamesAbbr: weekdayNamesBasqueAbbr},
1069: {tags: []string{"eu-ES"}, localMonth: localMonthsNameBasque, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBasque, weekdayNamesAbbr: weekdayNamesBasqueAbbr},
35: {tags: []string{"be"}, localMonth: localMonthsNameBelarusian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBelarusian, weekdayNamesAbbr: weekdayNamesBelarusianAbbr},
1059: {tags: []string{"be-BY"}, localMonth: localMonthsNameBelarusian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBelarusian, weekdayNamesAbbr: weekdayNamesBelarusianAbbr},
25626: {tags: []string{"bs-Cyrl"}, localMonth: localMonthsNameBosnianCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnianCyrillic, weekdayNamesAbbr: weekdayNamesBosnianCyrillicAbbr},
8218: {tags: []string{"bs-Cyrl-BA"}, localMonth: localMonthsNameBosnianCyrillic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnianCyrillic, weekdayNamesAbbr: weekdayNamesBosnianCyrillicAbbr},
26650: {tags: []string{"bs-Latn"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr},
30746: {tags: []string{"bs"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr},
5146: {tags: []string{"bs-Latn-BA"}, localMonth: localMonthsNameBosnian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBosnian, weekdayNamesAbbr: weekdayNamesBosnianAbbr},
126: {tags: []string{"br"}, localMonth: localMonthsNameBreton, apFmt: apFmtBreton, weekdayNames: weekdayNamesBreton, weekdayNamesAbbr: weekdayNamesBretonAbbr},
1150: {tags: []string{"br-FR"}, localMonth: localMonthsNameBreton, apFmt: apFmtBreton, weekdayNames: weekdayNamesBreton, weekdayNamesAbbr: weekdayNamesBretonAbbr},
2: {tags: []string{"bg"}, localMonth: localMonthsNameBulgarian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBulgarian, weekdayNamesAbbr: weekdayNamesBulgarianAbbr},
1026: {tags: []string{"bg-BG"}, localMonth: localMonthsNameBulgarian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesBulgarian, weekdayNamesAbbr: weekdayNamesBulgarianAbbr},
85: {tags: []string{"my"}, localMonth: localMonthsNameBurmese, apFmt: apFmtBurmese, weekdayNames: weekdayNamesBurmese, weekdayNamesAbbr: weekdayNamesBurmese},
1109: {tags: []string{"my-MM"}, localMonth: localMonthsNameBurmese, apFmt: apFmtBurmese, weekdayNames: weekdayNamesBurmese, weekdayNamesAbbr: weekdayNamesBurmese},
3: {tags: []string{"ca"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr},
1027: {tags: []string{"ca-ES"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr},
1119: {tags: []string{"tzm-Arab-MA"}, localMonth: localMonthsNameArabicIraq, apFmt: apFmtArabic, weekdayNames: weekdayNamesArabic, weekdayNamesAbbr: weekdayNamesArabicAbbr},
146: {tags: []string{"ku"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish},
31890: {tags: []string{"ku-Arab"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish},
1170: {tags: []string{"ku-Arab-IQ"}, localMonth: localMonthsNameCentralKurdish, apFmt: apFmtCentralKurdish, weekdayNames: weekdayNamesCentralKurdish, weekdayNamesAbbr: weekdayNamesCentralKurdish},
92: {tags: []string{"chr"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr},
31836: {tags: []string{"chr-Cher"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr},
1116: {tags: []string{"chr-Cher-US"}, localMonth: localMonthsNameCherokee, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCherokee, weekdayNamesAbbr: weekdayNamesCherokeeAbbr},
4: {tags: []string{"zh-Hans"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2},
30724: {tags: []string{"zh"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2},
2052: {tags: []string{"zh-CN"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr},
4100: {tags: []string{"zh-SG"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr},
31748: {tags: []string{"zh-Hant"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2},
3076: {tags: []string{"zh-HK"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2},
5124: {tags: []string{"zh-MO"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2},
1028: {tags: []string{"zh-TW"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2], weekdayNames: weekdayNamesChinese, weekdayNamesAbbr: weekdayNamesChineseAbbr2, useGannen: true},
131: {tags: []string{"co"}, localMonth: localMonthsNameCorsican, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCorsican, weekdayNamesAbbr: weekdayNamesCorsicanAbbr},
1155: {tags: []string{"co-FR"}, localMonth: localMonthsNameCorsican, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCorsican, weekdayNamesAbbr: weekdayNamesCorsicanAbbr},
26: {tags: []string{"hr"}, localMonth: localMonthsNameCroatian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr},
1050: {tags: []string{"hr-HR"}, localMonth: localMonthsNameCroatian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr},
4122: {tags: []string{"hr-BA"}, localMonth: localMonthsNameCroatianLatin, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesCroatian, weekdayNamesAbbr: weekdayNamesCroatianAbbr},
5: {tags: []string{"cs"}, localMonth: localMonthsNameCzech, apFmt: apFmtCzech, weekdayNames: weekdayNamesCzech, weekdayNamesAbbr: weekdayNamesCzechAbbr},
1029: {tags: []string{"cs-CZ"}, localMonth: localMonthsNameCzech, apFmt: apFmtCzech, weekdayNames: weekdayNamesCzech, weekdayNamesAbbr: weekdayNamesCzechAbbr},
6: {tags: []string{"da"}, localMonth: localMonthsNameDanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDanish, weekdayNamesAbbr: weekdayNamesDanishAbbr},
1030: {tags: []string{"da-DK"}, localMonth: localMonthsNameDanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDanish, weekdayNamesAbbr: weekdayNamesDanishAbbr},
140: {tags: []string{"prs"}, localMonth: localMonthsNameDari, apFmt: apFmtPersian, weekdayNames: weekdayNamesDari, weekdayNamesAbbr: weekdayNamesDari},
1164: {tags: []string{"prs-AF"}, localMonth: localMonthsNameDariAfghanistan, apFmt: apFmtDari, weekdayNames: weekdayNamesDari, weekdayNamesAbbr: weekdayNamesDari},
101: {tags: []string{"dv"}, localMonth: localMonthsNameDivehi, apFmt: apFmtDivehi, weekdayNames: weekdayNamesDivehi, weekdayNamesAbbr: weekdayNamesDivehi},
1125: {tags: []string{"dv-MV"}, localMonth: localMonthsNameDivehi, apFmt: apFmtDivehi, weekdayNames: weekdayNamesDivehi, weekdayNamesAbbr: weekdayNamesDivehi},
19: {tags: []string{"nl"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr},
2067: {tags: []string{"nl-BE"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr},
1043: {tags: []string{"nl-NL"}, localMonth: localMonthsNameDutch, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesDutch, weekdayNamesAbbr: weekdayNamesDutchAbbr},
3153: {tags: []string{"dz-BT"}, localMonth: localMonthsNameDzongkha, apFmt: apFmtDzongkha, weekdayNames: weekdayNamesDzongkha, weekdayNamesAbbr: weekdayNamesDzongkhaAbbr},
9: {tags: []string{"en"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
4096: {tags: []string{
"aa", "aa-DJ", "aa-ER", "aa-ET", "af-NA", "agq", "agq-CM", "ak",
"ak-GH", "sq-MK", "gsw-LI", "gsw-CH", "ar-TD", "ar-KM", "ar-DJ",
"ar-ER", "ar-IL", "ar-MR", "ar-PS", "ar-SO", "ar-SS", "ar-SD",
"ar-001", "ast", "ast-ES", "asa", "asa-TZ", "ksf", "ksf-CM", "bm",
"bm-Latn-ML", "bas", "bas-CM", "bem", "bem-ZM", "bez", "bez-TZ",
"bho", "bho-Deva", "bho-Deva-IN", "byn", "byn-ER", "brx", "brx-IN",
"ca-AD", "ca-FR", "ca-IT", "ceb", "ceb-Latn", "ceb-Latn-PH",
"tzm-Latn-MA", "ccp", "ccp-Cakm", "ccp-Cakm-BD", "ccp-Cakm-IN",
"ce-RU", "cgg", "cgg-UG", "cu-RU", "cv", "cv-Cyrl", "cv-Cyrl-RU",
"swc", "swc-CD", "kw", "kw-GB", "da-GL", "dua", "dua-CM", "nl-AW",
"nl-BQ", "nl-CW", "nl-SX", "nl-SR", "dz", "ebu", "ebu-KE", "en-AS",
"en-AI", "en-AG", "en-AT", "en-BS", "en-BB", "en-BE", "en-BM",
"en-BW", "en-IO", "en-VG", "en-BI", "en-CM", "en-KY", "en-CX",
"en-CC", "en-CK", "en-CY", "en-DK", "en-DM", "en-ER", "en-150",
"en-FK", "en-FI", "en-FJ", "en-GM", "en-DE", "en-GH", "en-GI",
"en-GD", "en-GU", "en-GG", "en-GY", "en-IM", "en-IL", "en-JE",
"en-KE", "en-KI", "en-LS", "en-LR", "en-MO", "en-MG", "en-MW",
"en-MV", "en-MT", "en-MH", "en-MU", "en-FM", "en-MS", "en-NA",
"en-NR", "en-NL", "en-NG", "en-NU", "en-NF", "en-MP", "en-PK",
"en-PW", "en-PG", "en-PN", "en-PR", "en-RW", "en-KN", "en-LC",
"en-VC", "en-WS", "en-SC", "en-SL", "en-SX", "en-SI", "en-SB",
"en-SS", "en-SH", "en-SD", "en-SZ", "en-SE", "en-CH", "en-TZ",
"en-TK", "en-TO", "en-TC", "en-TV", "en-UG", "en-UM", "en-VI",
"en-VU", "en-001", "en-ZM", "eo", "eo-001", "ee", "ee-GH", "ee-TG",
"ewo", "ewo-CM", "fo-DK", "fr-DZ", "fr-BJ", "fr-BF", "fr-BI",
"fr-CF", "fr-TD", "fr-KM", "fr-CG", "fr-DJ", "fr-GQ", "fr-GF",
"fr-PF", "fr-GA", "fr-GP", "fr-GN", "fr-MG", "fr-MQ", "fr-MR",
"fr-MU", "fr-YT", "fr-NC", "fr-NE", "fr-RW", "fr-BL", "fr-MF",
"fr-PM", "fr-SC", "fr-SY", "fr-TG", "fr-TN", "fr-VU", "fr-WF",
"fur", "fur-IT", "ff-Latn-BF", "ff-CM", "ff-Latn-CM", "ff-Latn-GM",
"ff-Latn-GH", "ff-GN", "ff-Latn-GN", "ff-Latn-GW", "ff-Latn-LR",
"ff-MR", "ff-Latn-MR", "ff-Latn-NE", "ff-Latn-SL", "lg", "lg-UG",
"de-BE", "de-IT", "el-CY", "guz", "guz-KE", "bgc", "bgc-Deva",
"bgc-Deva-IN", "ha-Latn-GH", "ha-Latn-NE", "hi-Latn", "hi-Latn-IN",
"ia", "ia-FR", "ia-001", "it-SM", "it-VA", "jv", "jv-Latn",
"jv-Latn-ID", "dyo", "dyo-SN", "kea", "kea-CV", "kab", "kab-DZ",
"kgp", "kgp-Latn", "kgp-Latn-BR", "kkj", "kkj-CM", "kln", "kln-KE",
"kam", "kam-KE", "ks-Arab-IN", "ki", "ki-KE", "sw-TZ", "sw-UG",
"ko-KP", "khq", "khq-ML", "ses", "ses-ML", "nmg", "nmg-CM",
"ku-Arab-IR", "lkt", "lkt-US", "lag", "lag-TZ", "ln", "ln-AO",
"ln-CF", "ln-CG", "ln-CD", "nds", "nds-DE", "nds-NL", "lu", "lu-CD",
"luo", "luo-KE", "luy", "luy-KE", "jmc", "jmc-TZ", "mgh", "mgh-MZ",
"kde", "kde-TZ", "mg", "mg-MG", "gv", "gv-IM", "mas", "mas-KE",
"mas-TZ", "mzn-IR", "mer", "mer-KE", "mgo", "mgo-CM", "mfe",
"mfe-MU", "mua", "mua-CM", "nqo", "nqo-GN", "naq", "naq-NA", "nnh",
"nnh-CM", "jgo", "jgo-CM", "yrl", "yrl-Latn", "yrl-Latn-BR",
"yrl-Latn-CO", "yrl-Latn-VE", "lrc-IQ", "lrc-IR", "nd", "nd-ZW",
"nb-SJ", "nus", "nus-SD", "nus-SS", "nyn", "nyn-UG", "oc-ES",
"om-KE", "os", "os-GE", "os-RU", "ps-PK", "fa-AF", "pt-AO", "pt-CV",
"pt-GQ", "pt-GW", "pt-LU", "pt-MO", "pt-MZ", "pt-ST", "pt-CH",
"pt-TL", "prg-001", "raj", "raj-Deva", "raj-Deva-IN", "ksh",
"ksh-DE", "rof", "rof-TZ", "rn", "rn-BI", "ru-BY", "ru-KZ", "ru-KG",
"ru-UA", "rwk", "rwk-TZ", "ssy", "ssy-ER", "saq", "saq-KE", "sg",
"sg-CF", "sbp", "sbp-TZ", "sc", "sc-Latn", "sc-Latn-IT", "seh",
"seh-MZ", "ksb", "ksb-TZ", "sn", "sn-Latn", "sn-Latn-ZW", "xog",
"xog-UG", "so-DJ", "so-ET", "so-KE", "nr", "nr-ZA", "st-LS",
"es-BZ", "es-BR", "es-GQ", "es-PH", "zgh", "zgh-Tfng-MA",
"zgh-Tfng", "ss", "ss-ZA", "ss-SZ", "sv-AX", "shi", "shi-Tfng",
"shi-Tfng-MA", "shi-Latn", "shi-Latn-MA", "dav", "dav-KE", "ta-MY",
"ta-SG", "twq", "twq-NE", "teo", "teo-KE", "teo-UG", "bo-IN", "tig",
"tig-ER", "to", "to-TO", "tr-CY", "uz-Arab", "uz-Arab-AF", "vai",
"vai-Vaii", "vai-Vaii-LR", "vai-Latn-LR", "vai-Latn", "vo",
"vo-001", "vun", "vun-TZ", "wae", "wae-CH", "wal", "wal-ET", "yav",
"yav-CM", "yo-BJ", "dje", "dje-NE",
}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
3081: {tags: []string{"en-AU"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
10249: {tags: []string{"en-BZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
4105: {tags: []string{"en-CA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
9225: {tags: []string{"en-029"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
15369: {tags: []string{"en-HK"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
16393: {tags: []string{"en-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
6153: {tags: []string{"en-IE"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
8201: {tags: []string{"en-JM"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
17417: {tags: []string{"en-MY"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
5129: {tags: []string{"en-NZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
13321: {tags: []string{"en-PH"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
18441: {tags: []string{"en-SG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
7177: {tags: []string{"en-ZA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
11273: {tags: []string{"en-TT"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
19465: {tags: []string{"en-AE"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
2057: {tags: []string{"en-GB"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0]), weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
1033: {tags: []string{"en-US"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
12297: {tags: []string{"en-ZW"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
37: {tags: []string{"et"}, localMonth: localMonthsNameEstonian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEstonian, weekdayNamesAbbr: weekdayNamesEstonianAbbr},
1061: {tags: []string{"et-EE"}, localMonth: localMonthsNameEstonian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEstonian, weekdayNamesAbbr: weekdayNamesEstonianAbbr},
56: {tags: []string{"fo"}, localMonth: localMonthsNameFaroese, apFmt: apFmtFaroese, weekdayNames: weekdayNamesFaroese, weekdayNamesAbbr: weekdayNamesFaroeseAbbr},
1080: {tags: []string{"fo-FO"}, localMonth: localMonthsNameFaroese, apFmt: apFmtFaroese, weekdayNames: weekdayNamesFaroese, weekdayNamesAbbr: weekdayNamesFaroeseAbbr},
100: {tags: []string{"fil"}, localMonth: localMonthsNameFilipino, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFilipino, weekdayNamesAbbr: weekdayNamesFilipinoAbbr},
1124: {tags: []string{"fil-PH"}, localMonth: localMonthsNameFilipino, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFilipino, weekdayNamesAbbr: weekdayNamesFilipinoAbbr},
11: {tags: []string{"fi"}, localMonth: localMonthsNameFinnish, apFmt: apFmtFinnish, weekdayNames: weekdayNamesFinnish, weekdayNamesAbbr: weekdayNamesFinnishAbbr},
1035: {tags: []string{"fi-FI"}, localMonth: localMonthsNameFinnish, apFmt: apFmtFinnish, weekdayNames: weekdayNamesFinnish, weekdayNamesAbbr: weekdayNamesFinnishAbbr},
12: {tags: []string{"fr"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
2060: {tags: []string{"fr-BE"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
11276: {tags: []string{"fr-CM"}, localMonth: localMonthsNameFrench, apFmt: apFmtCameroon, weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
3084: {tags: []string{"fr-CA"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
7180: {tags: []string{"fr-029"}, localMonth: localMonthsNameCaribbean, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
9228: {tags: []string{"fr-CD"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
12300: {tags: []string{"fr-CI"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
1036: {tags: []string{"fr-FR"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
15372: {tags: []string{"fr-HT"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
5132: {tags: []string{"fr-LU"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
13324: {tags: []string{"fr-ML"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
14348: {tags: []string{"fr-MA"}, localMonth: localMonthsNameMorocco, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
6156: {tags: []string{"fr-MC"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
8204: {tags: []string{"fr-RE"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
10252: {tags: []string{"fr-SN"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
4108: {tags: []string{"fr-CH"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrench, weekdayNamesAbbr: weekdayNamesFrenchAbbr},
98: {tags: []string{"fy"}, localMonth: localMonthsNameFrisian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrisian, weekdayNamesAbbr: weekdayNamesFrisianAbbr},
1122: {tags: []string{"fy-NL"}, localMonth: localMonthsNameFrisian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFrisian, weekdayNamesAbbr: weekdayNamesFrisianAbbr},
103: {tags: []string{"ff"}, localMonth: localMonthsNameFulah, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFulah, weekdayNamesAbbr: weekdayNamesFulahAbbr},
31847: {tags: []string{"ff-Latn"}, localMonth: localMonthsNameFulah, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesFulah, weekdayNamesAbbr: weekdayNamesFulahAbbr},
1127: {tags: []string{"ff-NG", "ff-Latn-NG"}, localMonth: localMonthsNameNigeria, apFmt: apFmtNigeria, weekdayNames: weekdayNamesNigeria, weekdayNamesAbbr: weekdayNamesNigeriaAbbr},
2151: {tags: []string{"ff-SN", "ff-Latn-SN"}, localMonth: localMonthsNameNigeria, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesNigeria, weekdayNamesAbbr: weekdayNamesNigeriaAbbr},
86: {tags: []string{"gl"}, localMonth: localMonthsNameGalician, apFmt: apFmtCuba, weekdayNames: weekdayNamesGalician, weekdayNamesAbbr: weekdayNamesGalicianAbbr},
1110: {tags: []string{"gl-ES"}, localMonth: localMonthsNameGalician, apFmt: apFmtCuba, weekdayNames: weekdayNamesGalician, weekdayNamesAbbr: weekdayNamesGalicianAbbr},
55: {tags: []string{"ka"}, localMonth: localMonthsNameGeorgian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGeorgian, weekdayNamesAbbr: weekdayNamesGeorgianAbbr},
1079: {tags: []string{"ka-GE"}, localMonth: localMonthsNameGeorgian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGeorgian, weekdayNamesAbbr: weekdayNamesGeorgianAbbr},
7: {tags: []string{"de"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
3079: {tags: []string{"de-AT"}, localMonth: localMonthsNameAustria, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
1031: {tags: []string{"de-DE"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
5127: {tags: []string{"de-LI"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
4103: {tags: []string{"de-LU"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
2055: {tags: []string{"de-CH"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGerman, weekdayNamesAbbr: weekdayNamesGermanAbbr},
8: {tags: []string{"el"}, localMonth: localMonthsNameGreek, apFmt: apFmtGreek, weekdayNames: weekdayNamesGreek, weekdayNamesAbbr: weekdayNamesGreekAbbr},
1032: {tags: []string{"el-GR"}, localMonth: localMonthsNameGreek, apFmt: apFmtGreek, weekdayNames: weekdayNamesGreek, weekdayNamesAbbr: weekdayNamesGreekAbbr},
111: {tags: []string{"kl"}, localMonth: localMonthsNameGreenlandic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGreenlandic, weekdayNamesAbbr: weekdayNamesGreenlandicAbbr},
1135: {tags: []string{"kl-GL"}, localMonth: localMonthsNameGreenlandic, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesGreenlandic, weekdayNamesAbbr: weekdayNamesGreenlandicAbbr},
116: {tags: []string{"gn"}, localMonth: localMonthsNameGuarani, apFmt: apFmtCuba, weekdayNames: weekdayNamesGuarani, weekdayNamesAbbr: weekdayNamesGuaraniAbbr},
1140: {tags: []string{"gn-PY"}, localMonth: localMonthsNameGuarani, apFmt: apFmtCuba, weekdayNames: weekdayNamesGuarani, weekdayNamesAbbr: weekdayNamesGuaraniAbbr},
71: {tags: []string{"gu"}, localMonth: localMonthsNameGujarati, apFmt: apFmtGujarati, weekdayNames: weekdayNamesGujarati, weekdayNamesAbbr: weekdayNamesGujaratiAbbr},
1095: {tags: []string{"gu-IN"}, localMonth: localMonthsNameGujarati, apFmt: apFmtGujarati, weekdayNames: weekdayNamesGujarati, weekdayNamesAbbr: weekdayNamesGujaratiAbbr},
104: {tags: []string{"ha"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr},
31848: {tags: []string{"ha-Latn"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr},
1128: {tags: []string{"ha-Latn-NG"}, localMonth: localMonthsNameHausa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHausa, weekdayNamesAbbr: weekdayNamesHausaAbbr},
117: {tags: []string{"haw"}, localMonth: localMonthsNameHawaiian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHawaiian, weekdayNamesAbbr: weekdayNamesHawaiianAbbr},
1141: {tags: []string{"haw-US"}, localMonth: localMonthsNameHawaiian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHawaiian, weekdayNamesAbbr: weekdayNamesHawaiianAbbr},
13: {tags: []string{"he"}, localMonth: localMonthsNameHebrew, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHebrew, weekdayNamesAbbr: weekdayNamesHebrewAbbr},
1037: {tags: []string{"he-IL"}, localMonth: localMonthsNameHebrew, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesHebrew, weekdayNamesAbbr: weekdayNamesHebrewAbbr},
57: {tags: []string{"hi"}, localMonth: localMonthsNameHindi, apFmt: apFmtHindi, weekdayNames: weekdayNamesHindi, weekdayNamesAbbr: weekdayNamesHindiAbbr},
1081: {tags: []string{"hi-IN"}, localMonth: localMonthsNameHindi, apFmt: apFmtHindi, weekdayNames: weekdayNamesHindi, weekdayNamesAbbr: weekdayNamesHindiAbbr},
14: {tags: []string{"hu"}, localMonth: localMonthsNameHungarian, apFmt: apFmtHungarian, weekdayNames: weekdayNamesHungarian, weekdayNamesAbbr: weekdayNamesHungarianAbbr},
1038: {tags: []string{"hu-HU"}, localMonth: localMonthsNameHungarian, apFmt: apFmtHungarian, weekdayNames: weekdayNamesHungarian, weekdayNamesAbbr: weekdayNamesHungarianAbbr},
15: {tags: []string{"is"}, localMonth: localMonthsNameIcelandic, apFmt: apFmtIcelandic, weekdayNames: weekdayNamesIcelandic, weekdayNamesAbbr: weekdayNamesIcelandicAbbr},
1039: {tags: []string{"is-IS"}, localMonth: localMonthsNameIcelandic, apFmt: apFmtIcelandic, weekdayNames: weekdayNamesIcelandic, weekdayNamesAbbr: weekdayNamesIcelandicAbbr},
112: {tags: []string{"ig"}, localMonth: localMonthsNameIgbo, apFmt: apFmtIgbo, weekdayNames: weekdayNamesIgbo, weekdayNamesAbbr: weekdayNamesIgboAbbr},
1136: {tags: []string{"ig-NG"}, localMonth: localMonthsNameIgbo, apFmt: apFmtIgbo, weekdayNames: weekdayNamesIgbo, weekdayNamesAbbr: weekdayNamesIgboAbbr},
33: {tags: []string{"id"}, localMonth: localMonthsNameIndonesian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesIndonesian, weekdayNamesAbbr: weekdayNamesIndonesianAbbr},
1057: {tags: []string{"id-ID"}, localMonth: localMonthsNameIndonesian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesIndonesian, weekdayNamesAbbr: weekdayNamesIndonesianAbbr},
93: {tags: []string{"iu"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr},
31837: {tags: []string{"iu-Latn"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr},
2141: {tags: []string{"iu-Latn-CA"}, localMonth: localMonthsNameInuktitut, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesInuktitut, weekdayNamesAbbr: weekdayNamesInuktitutAbbr},
30813: {tags: []string{"iu-Cans"}, localMonth: localMonthsNameSyllabics, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSyllabics, weekdayNamesAbbr: weekdayNamesSyllabicsAbbr},
1117: {tags: []string{"iu-Cans-CA"}, localMonth: localMonthsNameSyllabics, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSyllabics, weekdayNamesAbbr: weekdayNamesSyllabicsAbbr},
60: {tags: []string{"ga"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish, weekdayNames: weekdayNamesIrish, weekdayNamesAbbr: weekdayNamesIrishAbbr},
2108: {tags: []string{"ga-IE"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish, weekdayNames: weekdayNamesIrish, weekdayNamesAbbr: weekdayNamesIrishAbbr},
16: {tags: []string{"it"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr},
1040: {tags: []string{"it-IT"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr},
2064: {tags: []string{"it-CH"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesItalian, weekdayNamesAbbr: weekdayNamesItalianAbbr},
17: {tags: []string{"ja"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr},
1041: {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr},
75: {tags: []string{"kn"}, localMonth: localMonthsNameKannada, apFmt: apFmtKannada, weekdayNames: weekdayNamesKannada, weekdayNamesAbbr: weekdayNamesKannadaAbbr},
1099: {tags: []string{"kn-IN"}, localMonth: localMonthsNameKannada, apFmt: apFmtKannada, weekdayNames: weekdayNamesKannada, weekdayNamesAbbr: weekdayNamesKannadaAbbr},
1137: {tags: []string{"kr-Latn-NG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
96: {tags: []string{"ks"}, localMonth: localMonthsNameKashmiri, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKashmiri, weekdayNamesAbbr: weekdayNamesKashmiriAbbr},
1120: {tags: []string{"ks-Arab"}, localMonth: localMonthsNameKashmiri, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKashmiri, weekdayNamesAbbr: weekdayNamesKashmiriAbbr},
2144: {tags: []string{"ks-Deva-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesEnglish, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
63: {tags: []string{"kk"}, localMonth: localMonthsNameKazakh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKazakh, weekdayNamesAbbr: weekdayNamesKazakhAbbr},
1087: {tags: []string{"kk-KZ"}, localMonth: localMonthsNameKazakh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKazakh, weekdayNamesAbbr: weekdayNamesKazakhAbbr},
83: {tags: []string{"km"}, localMonth: localMonthsNameKhmer, apFmt: apFmtKhmer, weekdayNames: weekdayNamesKhmer, weekdayNamesAbbr: weekdayNamesKhmerAbbr},
1107: {tags: []string{"km-KH"}, localMonth: localMonthsNameKhmer, apFmt: apFmtKhmer, weekdayNames: weekdayNamesKhmer, weekdayNamesAbbr: weekdayNamesKhmerAbbr},
134: {tags: []string{"quc"}, localMonth: localMonthsNameKiche, apFmt: apFmtCuba, weekdayNames: weekdayNamesKiche, weekdayNamesAbbr: weekdayNamesKicheAbbr},
1158: {tags: []string{"quc-Latn-GT"}, localMonth: localMonthsNameKiche, apFmt: apFmtCuba, weekdayNames: weekdayNamesKiche, weekdayNamesAbbr: weekdayNamesKicheAbbr},
135: {tags: []string{"rw"}, localMonth: localMonthsNameKinyarwanda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKinyarwanda, weekdayNamesAbbr: weekdayNamesKinyarwandaAbbr},
1159: {tags: []string{"rw-RW"}, localMonth: localMonthsNameKinyarwanda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKinyarwanda, weekdayNamesAbbr: weekdayNamesKinyarwandaAbbr},
65: {tags: []string{"sw"}, localMonth: localMonthsNameKiswahili, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKiswahili, weekdayNamesAbbr: weekdayNamesKiswahiliAbbr},
1089: {tags: []string{"sw-KE"}, localMonth: localMonthsNameKiswahili, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesKiswahili, weekdayNamesAbbr: weekdayNamesKiswahiliAbbr},
87: {tags: []string{"kok"}, localMonth: localMonthsNameKonkani, apFmt: apFmtKonkani, weekdayNames: weekdayNamesKonkani, weekdayNamesAbbr: weekdayNamesKonkaniAbbr},
1111: {tags: []string{"kok-IN"}, localMonth: localMonthsNameKonkani, apFmt: apFmtKonkani, weekdayNames: weekdayNamesKonkani, weekdayNamesAbbr: weekdayNamesKonkaniAbbr},
18: {tags: []string{"ko"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean, weekdayNames: weekdayNamesKorean, weekdayNamesAbbr: weekdayNamesKoreanAbbr},
1042: {tags: []string{"ko-KR"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean, weekdayNames: weekdayNamesKorean, weekdayNamesAbbr: weekdayNamesKoreanAbbr},
64: {tags: []string{"ky"}, localMonth: localMonthsNameKyrgyz, apFmt: apFmtKyrgyz, weekdayNames: weekdayNamesKyrgyz, weekdayNamesAbbr: weekdayNamesKyrgyzAbbr},
1088: {tags: []string{"ky-KG"}, localMonth: localMonthsNameKyrgyz, apFmt: apFmtKyrgyz, weekdayNames: weekdayNamesKyrgyz, weekdayNamesAbbr: weekdayNamesKyrgyzAbbr},
84: {tags: []string{"lo"}, localMonth: localMonthsNameLao, apFmt: apFmtLao, weekdayNames: weekdayNamesLao, weekdayNamesAbbr: weekdayNamesLaoAbbr},
1108: {tags: []string{"lo-LA"}, localMonth: localMonthsNameLao, apFmt: apFmtLao, weekdayNames: weekdayNamesLao, weekdayNamesAbbr: weekdayNamesLaoAbbr},
1142: {tags: []string{"la-VA"}, localMonth: localMonthsNameLatin, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLatin, weekdayNamesAbbr: weekdayNamesLatinAbbr},
38: {tags: []string{"lv"}, localMonth: localMonthsNameLatvian, apFmt: apFmtLatvian, weekdayNames: weekdayNamesLatvian, weekdayNamesAbbr: weekdayNamesLatvianAbbr},
1062: {tags: []string{"lv-LV"}, localMonth: localMonthsNameLatvian, apFmt: apFmtLatvian, weekdayNames: weekdayNamesLatvian, weekdayNamesAbbr: weekdayNamesLatvianAbbr},
39: {tags: []string{"lt"}, localMonth: localMonthsNameLithuanian, apFmt: apFmtLithuanian, weekdayNames: weekdayNamesLithuanian, weekdayNamesAbbr: weekdayNamesLithuanianAbbr},
1063: {tags: []string{"lt-LT"}, localMonth: localMonthsNameLithuanian, apFmt: apFmtLithuanian, weekdayNames: weekdayNamesLithuanian, weekdayNamesAbbr: weekdayNamesLithuanianAbbr},
31790: {tags: []string{"dsb"}, localMonth: localMonthsNameLowerSorbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLowerSorbian, weekdayNamesAbbr: weekdayNamesLowerSorbianAbbr},
2094: {tags: []string{"dsb-DE"}, localMonth: localMonthsNameLowerSorbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLowerSorbian, weekdayNamesAbbr: weekdayNamesLowerSorbianAbbr},
110: {tags: []string{"lb"}, localMonth: localMonthsNameLuxembourgish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLuxembourgish, weekdayNamesAbbr: weekdayNamesLuxembourgishAbbr},
1134: {tags: []string{"lb-LU"}, localMonth: localMonthsNameLuxembourgish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesLuxembourgish, weekdayNamesAbbr: weekdayNamesLuxembourgishAbbr},
47: {tags: []string{"mk"}, localMonth: localMonthsNameMacedonian, apFmt: apFmtMacedonian, weekdayNames: weekdayNamesMacedonian, weekdayNamesAbbr: weekdayNamesMacedonianAbbr},
1071: {tags: []string{"mk-MK"}, localMonth: localMonthsNameMacedonian, apFmt: apFmtMacedonian, weekdayNames: weekdayNamesMacedonian, weekdayNamesAbbr: weekdayNamesMacedonianAbbr},
62: {tags: []string{"ms"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr},
2110: {tags: []string{"ms-BN"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr},
1086: {tags: []string{"ms-MY"}, localMonth: localMonthsNameMalay, apFmt: apFmtMalay, weekdayNames: weekdayNamesMalay, weekdayNamesAbbr: weekdayNamesMalayAbbr},
76: {tags: []string{"ml"}, localMonth: localMonthsNameMalayalam, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMalayalam, weekdayNamesAbbr: weekdayNamesMalayalamAbbr},
1100: {tags: []string{"ml-IN"}, localMonth: localMonthsNameMalayalam, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMalayalam, weekdayNamesAbbr: weekdayNamesMalayalamAbbr},
58: {tags: []string{"mt"}, localMonth: localMonthsNameMaltese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMaltese, weekdayNamesAbbr: weekdayNamesMalteseAbbr},
1082: {tags: []string{"mt-MT"}, localMonth: localMonthsNameMaltese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMaltese, weekdayNamesAbbr: weekdayNamesMalteseAbbr},
129: {tags: []string{"mi"}, localMonth: localMonthsNameMaori, apFmt: apFmtCuba, weekdayNames: weekdayNamesMaori, weekdayNamesAbbr: weekdayNamesMaoriAbbr},
1153: {tags: []string{"mi-NZ"}, localMonth: localMonthsNameMaori, apFmt: apFmtCuba, weekdayNames: weekdayNamesMaori, weekdayNamesAbbr: weekdayNamesMaoriAbbr},
122: {tags: []string{"arn"}, localMonth: localMonthsNameMapudungun, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMapudungun, weekdayNamesAbbr: weekdayNamesMapudungunAbbr},
1146: {tags: []string{"arn-CL"}, localMonth: localMonthsNameMapudungun, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMapudungun, weekdayNamesAbbr: weekdayNamesMapudungunAbbr},
78: {tags: []string{"mr"}, localMonth: localMonthsNameMarathi, apFmt: apFmtKonkani, weekdayNames: weekdayNamesMarathi, weekdayNamesAbbr: weekdayNamesMarathiAbbr},
1102: {tags: []string{"mr-IN"}, localMonth: localMonthsNameMarathi, apFmt: apFmtKonkani, weekdayNames: weekdayNamesMarathi, weekdayNamesAbbr: weekdayNamesMarathiAbbr},
124: {tags: []string{"moh"}, localMonth: localMonthsNameMohawk, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMohawk, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
1148: {tags: []string{"moh-CA"}, localMonth: localMonthsNameMohawk, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesMohawk, weekdayNamesAbbr: weekdayNamesEnglishAbbr},
80: {tags: []string{"mn"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianAbbr},
30800: {tags: []string{"mn-Cyrl"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianCyrlAbbr},
1104: {tags: []string{"mn-MN"}, localMonth: localMonthsNameMongolian, apFmt: apFmtMongolian, weekdayNames: weekdayNamesMongolian, weekdayNamesAbbr: weekdayNamesMongolianCyrlAbbr},
31824: {tags: []string{"mn-Mong"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolian, weekdayNamesAbbr: weekdayNamesTraditionalMongolian},
2128: {tags: []string{"mn-Mong-CN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolian, weekdayNamesAbbr: weekdayNamesTraditionalMongolian},
3152: {tags: []string{"mn-Mong-MN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTraditionalMongolianMN, weekdayNamesAbbr: weekdayNamesTraditionalMongolianMN},
97: {tags: []string{"ne"}, localMonth: localMonthsNameNepali, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepali, weekdayNamesAbbr: weekdayNamesNepaliAbbr},
2145: {tags: []string{"ne-IN"}, localMonth: localMonthsNameNepaliIN, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepaliIN, weekdayNamesAbbr: weekdayNamesNepaliINAbbr},
1121: {tags: []string{"ne-NP"}, localMonth: localMonthsNameNepali, apFmt: apFmtHindi, weekdayNames: weekdayNamesNepali, weekdayNamesAbbr: weekdayNamesNepaliAbbr},
20: {tags: []string{"no"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianAbbr},
31764: {tags: []string{"nb"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianNOAbbr},
1044: {tags: []string{"nb-NO"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtCuba, weekdayNames: weekdayNamesNorwegian, weekdayNamesAbbr: weekdayNamesNorwegianNOAbbr},
30740: {tags: []string{"nn"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtNorwegian, weekdayNames: weekdayNamesNorwegianNynorsk, weekdayNamesAbbr: weekdayNamesNorwegianNynorskAbbr},
2068: {tags: []string{"nn-NO"}, localMonth: localMonthsNameNorwegian, apFmt: apFmtNorwegian, weekdayNames: weekdayNamesNorwegianNynorsk, weekdayNamesAbbr: weekdayNamesNorwegianNynorskAbbr},
130: {tags: []string{"oc"}, localMonth: localMonthsNameOccitan, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOccitan, weekdayNamesAbbr: weekdayNamesOccitanAbbr},
1154: {tags: []string{"oc-FR"}, localMonth: localMonthsNameOccitan, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOccitan, weekdayNamesAbbr: weekdayNamesOccitanAbbr},
72: {tags: []string{"or"}, localMonth: localMonthsNameOdia, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOdia, weekdayNamesAbbr: weekdayNamesOdiaAbbr},
1096: {tags: []string{"or-IN"}, localMonth: localMonthsNameOdia, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesOdia, weekdayNamesAbbr: weekdayNamesOdiaAbbr},
114: {tags: []string{"om"}, localMonth: localMonthsNameOromo, apFmt: apFmtOromo, weekdayNames: weekdayNamesOromo, weekdayNamesAbbr: weekdayNamesOromoAbbr},
1138: {tags: []string{"om-ET"}, localMonth: localMonthsNameOromo, apFmt: apFmtOromo, weekdayNames: weekdayNamesOromo, weekdayNamesAbbr: weekdayNamesOromoAbbr},
99: {tags: []string{"ps"}, localMonth: localMonthsNamePashto, apFmt: apFmtPashto, weekdayNames: weekdayNamesPashto, weekdayNamesAbbr: weekdayNamesPashto},
1123: {tags: []string{"ps-AF"}, localMonth: localMonthsNamePashto, apFmt: apFmtPashto, weekdayNames: weekdayNamesPashto, weekdayNamesAbbr: weekdayNamesPashto},
41: {tags: []string{"fa"}, localMonth: localMonthsNamePersian, apFmt: apFmtPersian, weekdayNames: weekdayNamesPersian, weekdayNamesAbbr: weekdayNamesPersian},
1065: {tags: []string{"fa-IR"}, localMonth: localMonthsNamePersian, apFmt: apFmtPersian, weekdayNames: weekdayNamesPersian, weekdayNamesAbbr: weekdayNamesPersian},
21: {tags: []string{"pl"}, localMonth: localMonthsNamePolish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPolish, weekdayNamesAbbr: weekdayNamesPolishAbbr},
1045: {tags: []string{"pl-PL"}, localMonth: localMonthsNamePolish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPolish, weekdayNamesAbbr: weekdayNamesPolishAbbr},
22: {tags: []string{"pt"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr},
1046: {tags: []string{"pt-BR"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr},
2070: {tags: []string{"pt-PT"}, localMonth: localMonthsNamePortuguese, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPortuguese, weekdayNamesAbbr: weekdayNamesPortugueseAbbr},
70: {tags: []string{"pa"}, localMonth: localMonthsNamePunjabi, apFmt: apFmtPunjabi, weekdayNames: weekdayNamesPunjabi, weekdayNamesAbbr: weekdayNamesPunjabiAbbr},
31814: {tags: []string{"pa-Arab"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPunjabiArab, weekdayNamesAbbr: weekdayNamesPunjabiArab},
1094: {tags: []string{"pa-IN"}, localMonth: localMonthsNamePunjabi, apFmt: apFmtPunjabi, weekdayNames: weekdayNamesPunjabi, weekdayNamesAbbr: weekdayNamesPunjabiAbbr},
2118: {tags: []string{"pa-Arab-PK"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesPunjabiArab, weekdayNamesAbbr: weekdayNamesPunjabiArab},
107: {tags: []string{"quz"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechua, weekdayNamesAbbr: weekdayNamesQuechuaAbbr},
1131: {tags: []string{"quz-BO"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechua, weekdayNamesAbbr: weekdayNamesQuechuaAbbr},
2155: {tags: []string{"quz-EC"}, localMonth: localMonthsNameQuechuaEcuador, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesQuechuaEcuador, weekdayNamesAbbr: weekdayNamesQuechuaEcuadorAbbr},
3179: {tags: []string{"quz-PE"}, localMonth: localMonthsNameQuechua, apFmt: apFmtCuba, weekdayNames: weekdayNamesQuechuaPeru, weekdayNamesAbbr: weekdayNamesQuechuaPeruAbbr},
24: {tags: []string{"ro"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianAbbr},
2072: {tags: []string{"ro-MD"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianMoldovaAbbr},
1048: {tags: []string{"ro-RO"}, localMonth: localMonthsNameRomanian, apFmt: apFmtCuba, weekdayNames: weekdayNamesRomanian, weekdayNamesAbbr: weekdayNamesRomanianAbbr},
23: {tags: []string{"rm"}, localMonth: localMonthsNameRomansh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRomansh, weekdayNamesAbbr: weekdayNamesRomanshAbbr},
1047: {tags: []string{"rm-CH"}, localMonth: localMonthsNameRomansh, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRomansh, weekdayNamesAbbr: weekdayNamesRomanshAbbr},
25: {tags: []string{"ru"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr},
2073: {tags: []string{"ru-MD"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr},
1049: {tags: []string{"ru-RU"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesRussian, weekdayNamesAbbr: weekdayNamesRussianAbbr},
133: {tags: []string{"sah"}, localMonth: localMonthsNameSakha, apFmt: apFmtSakha, weekdayNames: weekdayNamesSakha, weekdayNamesAbbr: weekdayNamesSakhaAbbr},
1157: {tags: []string{"sah-RU"}, localMonth: localMonthsNameSakha, apFmt: apFmtSakha, weekdayNames: weekdayNamesSakha, weekdayNamesAbbr: weekdayNamesSakhaAbbr},
28731: {tags: []string{"smn"}, localMonth: localMonthsNameSami, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSami, weekdayNamesAbbr: weekdayNamesSamiAbbr},
9275: {tags: []string{"smn-FI"}, localMonth: localMonthsNameSami, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSami, weekdayNamesAbbr: weekdayNamesSamiAbbr},
31803: {tags: []string{"smj"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSamiLule, weekdayNamesAbbr: weekdayNamesSamiSwedenAbbr},
4155: {tags: []string{"smj-NO"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSamiLule, weekdayNamesAbbr: weekdayNamesSamiSamiLuleAbbr},
5179: {tags: []string{"smj-SE"}, localMonth: localMonthsNameSamiLule, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSweden, weekdayNamesAbbr: weekdayNamesSamiSwedenAbbr},
59: {tags: []string{"se"}, localMonth: localMonthsNameSamiNorthern, apFmt: apFmtSamiNorthern, weekdayNames: weekdayNamesSamiNorthern, weekdayNamesAbbr: weekdayNamesSamiNorthernAbbr},
3131: {tags: []string{"se-FI"}, localMonth: localMonthsNameSamiNorthernFI, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiNorthernFI, weekdayNamesAbbr: weekdayNamesSamiNorthernFIAbbr},
1083: {tags: []string{"se-NO"}, localMonth: localMonthsNameSamiNorthern, apFmt: apFmtSamiNorthern, weekdayNames: weekdayNamesSamiNorthern, weekdayNamesAbbr: weekdayNamesSamiNorthernAbbr},
2107: {tags: []string{"se-SE"}, localMonth: localMonthsNameSamiNorthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiNorthernSE, weekdayNamesAbbr: weekdayNamesSamiNorthernSEAbbr},
29755: {tags: []string{"sms"}, localMonth: localMonthsNameSamiSkolt, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSkolt, weekdayNamesAbbr: weekdayNamesSamiSkoltAbbr},
8251: {tags: []string{"sms-FI"}, localMonth: localMonthsNameSamiSkolt, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSkolt, weekdayNamesAbbr: weekdayNamesSamiSkoltAbbr},
30779: {tags: []string{"sma"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr},
6203: {tags: []string{"sma-NO"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr},
7227: {tags: []string{"sma-SE"}, localMonth: localMonthsNameSamiSouthern, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSamiSouthern, weekdayNamesAbbr: weekdayNamesSamiSouthernAbbr},
79: {tags: []string{"sa"}, localMonth: localMonthsNameSanskrit, apFmt: apFmtSanskrit, weekdayNames: weekdayNamesSanskrit, weekdayNamesAbbr: weekdayNamesSanskritAbbr},
1103: {tags: []string{"sa-IN"}, localMonth: localMonthsNameSanskrit, apFmt: apFmtSanskrit, weekdayNames: weekdayNamesSanskrit, weekdayNamesAbbr: weekdayNamesSanskritAbbr},
145: {tags: []string{"gd"}, localMonth: localMonthsNameScottishGaelic, apFmt: apFmtScottishGaelic, weekdayNames: weekdayNamesGaelic, weekdayNamesAbbr: weekdayNamesGaelicAbbr},
1169: {tags: []string{"gd-GB"}, localMonth: localMonthsNameScottishGaelic, apFmt: apFmtScottishGaelic, weekdayNames: weekdayNamesGaelic, weekdayNamesAbbr: weekdayNamesGaelicAbbr},
27674: {tags: []string{"sr-Cyrl"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr},
7194: {tags: []string{"sr-Cyrl-BA"}, localMonth: localMonthsNameSerbianBA, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianBA, weekdayNamesAbbr: weekdayNamesSerbianBAAbbr},
12314: {tags: []string{"sr-Cyrl-ME"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianME, weekdayNamesAbbr: weekdayNamesSerbianBAAbbr},
10266: {tags: []string{"sr-Cyrl-RS"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr},
3098: {tags: []string{"sr-Cyrl-CS"}, localMonth: localMonthsNameSerbian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbian, weekdayNamesAbbr: weekdayNamesSerbianAbbr},
28698: {tags: []string{"sr-Latn"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr},
31770: {tags: []string{"sr"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr},
6170: {tags: []string{"sr-Latn-BA"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatinBA, weekdayNames: weekdayNamesSerbianLatinBA, weekdayNamesAbbr: weekdayNamesSerbianLatinBAAbbr},
11290: {tags: []string{"sr-Latn-ME"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatinBA, weekdayNames: weekdayNamesSerbianLatinME, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr},
9242: {tags: []string{"sr-Latn-RS"}, localMonth: localMonthsNameSerbianLatin, apFmt: apFmtSerbianLatin, weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinAbbr},
2074: {tags: []string{"sr-Latn-CS"}, localMonth: localMonthsNameSerbianLatinCS, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSerbianLatin, weekdayNamesAbbr: weekdayNamesSerbianLatinCSAbbr},
108: {tags: []string{"nso"}, localMonth: localMonthsNameSesothoSaLeboa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSesothoSaLeboa, weekdayNamesAbbr: weekdayNamesSesothoSaLeboaAbbr},
1132: {tags: []string{"nso-ZA"}, localMonth: localMonthsNameSesothoSaLeboa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSesothoSaLeboa, weekdayNamesAbbr: weekdayNamesSesothoSaLeboaAbbr},
50: {tags: []string{"tn"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr},
2098: {tags: []string{"tn-BW"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr},
1074: {tags: []string{"tn-ZA"}, localMonth: localMonthsNameSetswana, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSetswana, weekdayNamesAbbr: weekdayNamesSetswanaAbbr},
89: {tags: []string{"sd"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr},
31833: {tags: []string{"sd-Arab"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr},
2137: {tags: []string{"sd-Arab-PK"}, localMonth: localMonthsNameSindhi, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr},
91: {tags: []string{"si"}, localMonth: localMonthsNameSinhala, apFmt: apFmtSinhala, weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr},
1115: {tags: []string{"si-LK"}, localMonth: localMonthsNameSinhala, apFmt: apFmtSinhala, weekdayNames: weekdayNamesSindhi, weekdayNamesAbbr: weekdayNamesSindhiAbbr},
27: {tags: []string{"sk"}, localMonth: localMonthsNameSlovak, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSlovak, weekdayNamesAbbr: weekdayNamesSlovakAbbr},
1051: {tags: []string{"sk-SK"}, localMonth: localMonthsNameSlovak, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSlovak, weekdayNamesAbbr: weekdayNamesSlovakAbbr},
36: {tags: []string{"sl"}, localMonth: localMonthsNameSlovenian, apFmt: apFmtSlovenian, weekdayNames: weekdayNamesSlovenian, weekdayNamesAbbr: weekdayNamesSlovenianAbbr},
1060: {tags: []string{"sl-SI"}, localMonth: localMonthsNameSlovenian, apFmt: apFmtSlovenian, weekdayNames: weekdayNamesSlovenian, weekdayNamesAbbr: weekdayNamesSlovenianAbbr},
119: {tags: []string{"so"}, localMonth: localMonthsNameSomali, apFmt: apFmtSomali, weekdayNames: weekdayNamesSomali, weekdayNamesAbbr: weekdayNamesSomaliAbbr},
1143: {tags: []string{"so-SO"}, localMonth: localMonthsNameSomali, apFmt: apFmtSomali, weekdayNames: weekdayNamesSomali, weekdayNamesAbbr: weekdayNamesSomaliAbbr},
48: {tags: []string{"st"}, localMonth: localMonthsNameSotho, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSotho, weekdayNamesAbbr: weekdayNamesSothoAbbr},
1072: {tags: []string{"st-ZA"}, localMonth: localMonthsNameSotho, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSotho, weekdayNamesAbbr: weekdayNamesSothoAbbr},
10: {tags: []string{"es"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr},
11274: {tags: []string{"es-AR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
8202: {tags: []string{"es-VE"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
16394: {tags: []string{"es-BO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
13322: {tags: []string{"es-CL"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
9226: {tags: []string{"es-CO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
5130: {tags: []string{"es-CR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
23562: {tags: []string{"es-CU"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
7178: {tags: []string{"es-DO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
12298: {tags: []string{"es-EC"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
17418: {tags: []string{"es-SV"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
4106: {tags: []string{"es-GT"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
18442: {tags: []string{"es-HN"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
22538: {tags: []string{"es-419"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
2058: {tags: []string{"es-MX"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
19466: {tags: []string{"es-NI"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
6154: {tags: []string{"es-PA"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
15370: {tags: []string{"es-PY"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
10250: {tags: []string{"es-PE"}, localMonth: localMonthsNameSpanishPE, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
20490: {tags: []string{"es-PR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
1034: {tags: []string{"es-ES_tradnl"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr},
3082: {tags: []string{"es-ES"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishAbbr},
21514: {tags: []string{"es-US"}, localMonth: localMonthsNameSpanish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishUSAbbr},
14346: {tags: []string{"es-UY"}, localMonth: localMonthsNameSpanishPE, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesSpanish, weekdayNamesAbbr: weekdayNamesSpanishARAbbr},
29: {tags: []string{"sv"}, localMonth: localMonthsNameSwedish, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr},
2077: {tags: []string{"sv-FI"}, localMonth: localMonthsNameSwedishFI, apFmt: apFmtSwedish, weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr},
1053: {tags: []string{"sv-SE"}, localMonth: localMonthsNameSwedishFI, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesSwedish, weekdayNamesAbbr: weekdayNamesSwedishAbbr},
90: {tags: []string{"syr"}, localMonth: localMonthsNameSyriac, apFmt: apFmtSyriac, weekdayNames: weekdayNamesSyriac, weekdayNamesAbbr: weekdayNamesSyriacAbbr},
1114: {tags: []string{"syr-SY"}, localMonth: localMonthsNameSyriac, apFmt: apFmtSyriac, weekdayNames: weekdayNamesSyriac, weekdayNamesAbbr: weekdayNamesSyriacAbbr},
40: {tags: []string{"tg"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr},
31784: {tags: []string{"tg-Cyrl"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr},
1064: {tags: []string{"tg-Cyrl-TJ"}, localMonth: localMonthsNameTajik, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTajik, weekdayNamesAbbr: weekdayNamesTajikAbbr},
95: {tags: []string{"tzm"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr},
31839: {tags: []string{"tzm-Latn"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr},
2143: {tags: []string{"tzm-Latn-DZ"}, localMonth: localMonthsNameTamazight, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTamazight, weekdayNamesAbbr: weekdayNamesTamazightAbbr},
73: {tags: []string{"ta"}, localMonth: localMonthsNameTamil, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamil, weekdayNamesAbbr: weekdayNamesTamilAbbr},
1097: {tags: []string{"ta-IN"}, localMonth: localMonthsNameTamil, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamil, weekdayNamesAbbr: weekdayNamesTamilAbbr},
2121: {tags: []string{"ta-LK"}, localMonth: localMonthsNameTamilLK, apFmt: apFmtTamil, weekdayNames: weekdayNamesTamilLK, weekdayNamesAbbr: weekdayNamesTamilLKAbbr},
68: {tags: []string{"tt"}, localMonth: localMonthsNameTatar, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTatar, weekdayNamesAbbr: weekdayNamesTatarAbbr},
1092: {tags: []string{"tt-RU"}, localMonth: localMonthsNameTatar, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTatar, weekdayNamesAbbr: weekdayNamesTatarAbbr},
74: {tags: []string{"te"}, localMonth: localMonthsNameTelugu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTelugu, weekdayNamesAbbr: weekdayNamesTeluguAbbr},
1098: {tags: []string{"te-IN"}, localMonth: localMonthsNameTelugu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTelugu, weekdayNamesAbbr: weekdayNamesTeluguAbbr},
30: {tags: []string{"th"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesThai, weekdayNamesAbbr: weekdayNamesThaiAbbr},
1054: {tags: []string{"th-TH"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesThai, weekdayNamesAbbr: weekdayNamesThaiAbbr},
81: {tags: []string{"bo"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan, weekdayNames: weekdayNamesTibetan, weekdayNamesAbbr: weekdayNamesTibetanAbbr},
1105: {tags: []string{"bo-CN"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan, weekdayNames: weekdayNamesTibetan, weekdayNamesAbbr: weekdayNamesTibetanAbbr},
115: {tags: []string{"ti"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinya, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr},
2163: {tags: []string{"ti-ER"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinyaER, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr},
1139: {tags: []string{"ti-ET"}, localMonth: localMonthsNameTigrinya, apFmt: apFmtTigrinya, weekdayNames: weekdayNamesTigrinya, weekdayNamesAbbr: weekdayNamesTigrinyaAbbr},
49: {tags: []string{"ts"}, localMonth: localMonthsNameTsonga, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTsonga, weekdayNamesAbbr: weekdayNamesTsongaAbbr},
1073: {tags: []string{"ts-ZA"}, localMonth: localMonthsNameTsonga, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTsonga, weekdayNamesAbbr: weekdayNamesTsongaAbbr},
31: {tags: []string{"tr"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish, weekdayNames: weekdayNamesTurkish, weekdayNamesAbbr: weekdayNamesTurkishAbbr},
1055: {tags: []string{"tr-TR"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish, weekdayNames: weekdayNamesTurkish, weekdayNamesAbbr: weekdayNamesTurkishAbbr},
66: {tags: []string{"tk"}, localMonth: localMonthsNameTurkmen, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTurkmen, weekdayNamesAbbr: weekdayNamesTurkmenAbbr},
1090: {tags: []string{"tk-TM"}, localMonth: localMonthsNameTurkmen, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesTurkmen, weekdayNamesAbbr: weekdayNamesTurkmenAbbr},
34: {tags: []string{"uk"}, localMonth: localMonthsNameUkrainian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUkrainian, weekdayNamesAbbr: weekdayNamesUkrainianAbbr},
1058: {tags: []string{"uk-UA"}, localMonth: localMonthsNameUkrainian, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUkrainian, weekdayNamesAbbr: weekdayNamesUkrainianAbbr},
46: {tags: []string{"hsb"}, localMonth: localMonthsNameUpperSorbian, apFmt: apFmtUpperSorbian, weekdayNames: weekdayNamesSorbian, weekdayNamesAbbr: weekdayNamesSorbianAbbr},
1070: {tags: []string{"hsb-DE"}, localMonth: localMonthsNameUpperSorbian, apFmt: apFmtUpperSorbian, weekdayNames: weekdayNamesSorbian, weekdayNamesAbbr: weekdayNamesSorbianAbbr},
32: {tags: []string{"ur"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUrdu, weekdayNamesAbbr: weekdayNamesUrdu},
2080: {tags: []string{"ur-IN"}, localMonth: localMonthsNamePunjabiArab, apFmt: apFmtUrdu, weekdayNames: weekdayNamesUrduIN, weekdayNamesAbbr: weekdayNamesUrduIN},
1056: {tags: []string{"ur-PK"}, localMonth: localMonthsNamePunjabiArab, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesUrdu, weekdayNamesAbbr: weekdayNamesUrdu},
128: {tags: []string{"ug"}, localMonth: localMonthsNameUyghur, apFmt: apFmtUyghur, weekdayNames: weekdayNamesUyghur, weekdayNamesAbbr: weekdayNamesUyghurAbbr},
1152: {tags: []string{"ug-CN"}, localMonth: localMonthsNameUyghur, apFmt: apFmtUyghur, weekdayNames: weekdayNamesUyghur, weekdayNamesAbbr: weekdayNamesUyghurAbbr},
30787: {tags: []string{"uz-Cyrl"}, localMonth: localMonthsNameUzbekCyrillic, apFmt: apFmtUzbekCyrillic, weekdayNames: weekdayNamesUzbekCyrillic, weekdayNamesAbbr: weekdayNamesUzbekCyrillicAbbr},
2115: {tags: []string{"uz-Cyrl-UZ"}, localMonth: localMonthsNameUzbekCyrillic, apFmt: apFmtUzbekCyrillic, weekdayNames: weekdayNamesUzbekCyrillic, weekdayNamesAbbr: weekdayNamesUzbekCyrillicAbbr},
67: {tags: []string{"uz"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr},
31811: {tags: []string{"uz-Latn"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr},
1091: {tags: []string{"uz-Latn-UZ"}, localMonth: localMonthsNameUzbek, apFmt: apFmtUzbek, weekdayNames: weekdayNamesUzbek, weekdayNamesAbbr: weekdayNamesUzbekAbbr},
2051: {tags: []string{"ca-ES-valencia"}, localMonth: localMonthsNameValencian, apFmt: apFmtSpanishAR, weekdayNames: weekdayNamesValencian, weekdayNamesAbbr: weekdayNamesValencianAbbr},
51: {tags: []string{"ve"}, localMonth: localMonthsNameVenda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesVenda, weekdayNamesAbbr: weekdayNamesVendaAbbr},
1075: {tags: []string{"ve-ZA"}, localMonth: localMonthsNameVenda, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesVenda, weekdayNamesAbbr: weekdayNamesVendaAbbr},
42: {tags: []string{"vi"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese, weekdayNames: weekdayNamesVietnamese, weekdayNamesAbbr: weekdayNamesVietnameseAbbr},
1066: {tags: []string{"vi-VN"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese, weekdayNames: weekdayNamesVietnamese, weekdayNamesAbbr: weekdayNamesVietnameseAbbr},
82: {tags: []string{"cy"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh, weekdayNames: weekdayNamesWelsh, weekdayNamesAbbr: weekdayNamesWelshAbbr},
1106: {tags: []string{"cy-GB"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh, weekdayNames: weekdayNamesWelsh, weekdayNamesAbbr: weekdayNamesWelshAbbr},
136: {tags: []string{"wo"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof, weekdayNames: weekdayNamesWolof, weekdayNamesAbbr: weekdayNamesWolofAbbr},
1160: {tags: []string{"wo-SN"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof, weekdayNames: weekdayNamesWolof, weekdayNamesAbbr: weekdayNamesWolofAbbr},
52: {tags: []string{"xh"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesXhosa, weekdayNamesAbbr: weekdayNamesXhosaAbbr},
1076: {tags: []string{"xh-ZA"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesXhosa, weekdayNamesAbbr: weekdayNamesXhosaAbbr},
120: {tags: []string{"ii"}, localMonth: localMonthsNameYi, apFmt: apFmtYi, weekdayNames: weekdayNamesYi, weekdayNamesAbbr: weekdayNamesYiAbbr},
1144: {tags: []string{"ii-CN"}, localMonth: localMonthsNameYi, apFmt: apFmtYi, weekdayNames: weekdayNamesYi, weekdayNamesAbbr: weekdayNamesYiAbbr},
1085: {tags: []string{"yi-001"}, localMonth: localMonthsNameYiddish, apFmt: apFmtYiddish, weekdayNames: weekdayNamesYiddish, weekdayNamesAbbr: weekdayNamesYiddishAbbr},
106: {tags: []string{"yo"}, localMonth: localMonthsNameYoruba, apFmt: apFmtYoruba, weekdayNames: weekdayNamesYoruba, weekdayNamesAbbr: weekdayNamesYorubaAbbr},
1130: {tags: []string{"yo-NG"}, localMonth: localMonthsNameYoruba, apFmt: apFmtYoruba, weekdayNames: weekdayNamesYoruba, weekdayNamesAbbr: weekdayNamesYorubaAbbr},
53: {tags: []string{"zu"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesZulu, weekdayNamesAbbr: weekdayNamesZuluAbbr},
1077: {tags: []string{"zu-ZA"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0], weekdayNames: weekdayNamesZulu, weekdayNamesAbbr: weekdayNamesZuluAbbr},
}
// supportedLanguageCodeInfo directly maps the supported language code and
// tags.
supportedLanguageCodeInfo = map[string]languageInfo{
"JA-JP-X-GANNEN": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr},
"JA-JP-X-GANNEN,80": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese, weekdayNames: weekdayNamesJapanese, weekdayNamesAbbr: weekdayNamesJapaneseAbbr, useGannen: true},
}
// republicOfChinaYear defined start time of the Republic of China
republicOfChinaYear = time.Date(1912, time.January, 1, 0, 0, 0, 0, time.UTC)
// republicOfChinaEraName defined the Republic of China era name for the
// Republic of China calendar.
republicOfChinaEraName = []string{"\u4E2D\u83EF\u6C11\u570B", "\u6C11\u570B", "\u524D"}
// japaneseEraYears list the Japanese era name periods.
japaneseEraYears = []time.Time{
time.Date(1868, time.August, 8, 0, 0, 0, 0, time.UTC),
time.Date(1912, time.June, 30, 0, 0, 0, 0, time.UTC),
time.Date(1926, time.November, 25, 0, 0, 0, 0, time.UTC),
time.Date(1989, time.January, 8, 0, 0, 0, 0, time.UTC),
time.Date(2019, time.April, 1, 0, 0, 0, 0, time.UTC),
}
// japaneseEraNames list the Japanese era name for the Japanese emperor
// reign calendar.
japaneseEraNames = []string{"\u660E\u6CBB", "\u5927\u6B63", "\u662D\u548C", "\u5E73\u6210", "\u4EE4\u548C"}
// japaneseEraYear list the Japanese era name symbols.
japaneseEraSymbols = []string{"M", "T", "S", "H", "R"}
// monthNamesAfrikaans list the month names in the Afrikaans.
monthNamesAfrikaans = []string{"Januarie", "Februarie", "Maart", "April", "Mei", "Junie", "Julie", "Augustus", "September", "Oktober", "November", "Desember"}
// monthNamesAfrikaansAbbr lists the month name abbreviations in the
// Afrikaans.
monthNamesAfrikaansAbbr = []string{"Jan.", "Feb.", "Maa.", "Apr.", "Mei", "Jun.", "Jul.", "Aug.", "Sep.", "Okt.", "Nov.", "Des."}
// monthNamesAlbanian list the month names in the Albanian.
monthNamesAlbanian = []string{"janar", "shkurt", "mars", "prill", "maj", "qershor", "korrik", "gusht", "shtator", "tetor", "nëntor", "dhjetor"}
// monthNamesAlbanianAbbr lists the month name abbreviations in the
// Albanian.
monthNamesAlbanianAbbr = []string{"jan", "shk", "mar", "pri", "maj", "qer", "krr", "gush", "sht", "tet", "nën", "dhj"}
// monthNamesAlsatian list the month names in the Alsatian.
monthNamesAlsatian = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "Auguscht", "Septämber", "Oktoober", "Novämber", "Dezämber"}
// monthNamesAlsatianAbbr lists the month name abbreviations in the
// Alsatian France.
monthNamesAlsatianAbbr = []string{"Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
// monthNamesAlsatianFrance list the month names in the Alsatian.
monthNamesAlsatianFrance = []string{"Jänner", "Feverje", "März", "Àpril", "Mai", "Jüni", "Jüli", "Augscht", "September", "Oktower", "Nowember", "Dezember"}
// monthNamesAlsatianFranceAbbr lists the month name abbreviations in the
// Alsatian France.
monthNamesAlsatianFranceAbbr = []string{"Jän.", "Fev.", "März", "Apr.", "Mai", "Jüni", "Jüli", "Aug.", "Sept.", "Okt.", "Now.", "Dez."}
// monthNamesAmharic list the month names in the Amharic.
monthNamesAmharic = []string{
"\u1303\u1295\u12E9\u12C8\u122A",
"\u134C\u1265\u1229\u12C8\u122A",
"\u121B\u122D\u127D",
"\u12A4\u1355\u122A\u120D",
"\u121C\u12ED",
"\u1301\u1295",
"\u1301\u120B\u12ED",
"\u12A6\u1308\u1235\u1275",
"\u1234\u1355\u1274\u121D\u1260\u122D",
"\u12A6\u12AD\u1276\u1260\u122D",
"\u1296\u126C\u121D\u1260\u122D",
"\u12F2\u1234\u121D\u1260\u122D",
}
// monthNamesAmharicAbbr lists the month name abbreviations in the Amharic.
monthNamesAmharicAbbr = []string{
"\u1303\u1295\u12E9",
"\u134C\u1265\u1229",
"\u121B\u122D\u127D",
"\u12A4\u1355\u122A",
"\u121C\u12ED",
"\u1301\u1295",
"\u1301\u120B\u12ED",
"\u12A6\u1308\u1235",
"\u1234\u1355\u1274",
"\u12A6\u12AD\u1276",
"\u1296\u126C\u121D",
"\u12F2\u1234\u121D",
}
// monthNamesArabic list the month names in the Arabic.
monthNamesArabic = []string{
"\u064A\u0646\u0627\u064A\u0631",
"\u0641\u0628\u0631\u0627\u064A\u0631",
"\u0645\u0627\u0631\u0633",
"\u0623\u0628\u0631\u064A\u0644",
"\u0645\u0627\u064A\u0648",
"\u064A\u0648\u0646\u064A\u0648",
"\u064A\u0648\u0644\u064A\u0648",
"\u0623\u063A\u0633\u0637\u0633",
"\u0633\u0628\u062A\u0645\u0628\u0631",
"\u0623\u0643\u062A\u0648\u0628\u0631",
"\u0646\u0648\u0641\u0645\u0628\u0631",
"\u062F\u064A\u0633\u0645\u0628\u0631",
}
// monthNamesArabicIraq list the month names in the Arabic Iraq.
monthNamesArabicIraq = []string{
"\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A",
"\u0634\u0628\u0627\u0637",
"\u0622\u0630\u0627\u0631",
"\u0646\u064A\u0633\u0627\u0646",
"\u0623\u064A\u0627\u0631",
"\u062D\u0632\u064A\u0631\u0627\u0646",
"\u062A\u0645\u0648\u0632",
"\u0622\u0628",
"\u0623\u064A\u0644\u0648\u0644",
"\u062A\u0634\u0631\u064A\u0646 \u0627\u0644\u0623\u0648\u0644",
"\u062A\u0634\u0631\u064A\u0646 \u0627\u0644\u062B\u0627\u0646\u064A",
"\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644",
}
// monthNamesArmenian list the month names in the Armenian.
monthNamesArmenian = []string{
"\u0540\u0578\u0582\u0576\u057E\u0561\u0580",
"\u0553\u0565\u057F\u0580\u057E\u0561\u0580",
"\u0544\u0561\u0580\u057F",
"\u0531\u057A\u0580\u056B\u056C",
"\u0544\u0561\u0575\u056B\u057D",
"\u0540\u0578\u0582\u0576\u056B\u057D",
"\u0540\u0578\u0582\u056C\u056B\u057D",
"\u0555\u0563\u0578\u057D\u057F\u0578\u057D",
"\u054D\u0565\u057A\u057F\u0565\u0574\u0562\u0565\u0580",
"\u0540\u0578\u056F\u057F\u0565\u0574\u0562\u0565\u0580",
"\u0546\u0578\u0575\u0565\u0574\u0562\u0565\u0580",
"\u0534\u0565\u056F\u057F\u0565\u0574\u0562\u0565\u0580",
}
// monthNamesArmenianAbbr lists the month name abbreviations in the
// Armenian.
monthNamesArmenianAbbr = []string{
"\u0540\u0576\u057E",
"\u0553\u057F\u057E",
"\u0544\u0580\u057F",
"\u0531\u057A\u0580",
"\u0544\u0575\u057D",
"\u0540\u0576\u057D",
"\u0540\u056C\u057D",
"\u0555\u0563\u057D",
"\u054D\u057A\u057F",
"\u0540\u056F\u057F",
"\u0546\u0575\u0574",
"\u0534\u056F\u057F",
}
// monthNamesAssamese list the month names in the Assamese.
monthNamesAssamese = []string{
"\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0",
"\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09F1\u09BE\u09F0\u09C0",
"\u09AE\u09BE\u09B0\u09CD\u099A",
"\u098F\u09AA\u09CD\u09B0\u09BF\u09B2",
"\u09AE\u09C7",
"\u099C\u09C1\u09A8",
"\u099C\u09C1\u09B2\u09BE\u0987",
"\u0986\u0997\u09B7\u09CD\u099F",
"\u099A\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09F0",
"\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09F0",
"\u09A8\u09AC\u09C7\u09AE\u09CD\u09AC\u09F0",
"\u09A1\u09BF\u099A\u09C7\u09AE\u09CD\u09AC\u09F0",
}
// monthNamesAssameseAbbr lists the month name abbreviations in the
// Assamese.
monthNamesAssameseAbbr = []string{
"\u099C\u09BE\u09A8\u09C1",
"\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1",
"\u09AE\u09BE\u09B0\u09CD\u099A",
"\u098F\u09AA\u09CD\u09B0\u09BF\u09B2",
"\u09AE\u09C7",
"\u099C\u09C1\u09A8",
"\u099C\u09C1\u09B2\u09BE\u0987",
"\u0986\u0997\u09B7\u09CD\u099F",
"\u099A\u09C7\u09AA\u09CD\u099F\u09C7",
"\u0985\u0995\u09CD\u099F\u09CB",
"\u09A8\u09AC\u09C7",
"\u09A1\u09BF\u099A\u09C7",
}
// monthNamesAzerbaijaniCyrillic list the month names in the Azerbaijani
// (Cyrillic).
monthNamesAzerbaijaniCyrillic = []string{
"j\u0430\u043D\u0432\u0430\u0440",
"\u0444\u0435\u0432\u0440\u0430\u043B",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0435\u043B",
"\u043C\u0430\u0458",
"\u0438\u0458\u0443\u043D",
"\u0438\u0458\u0443\u043B",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043D\u0442\u0458\u0430\u0431\u0440",
"\u043E\u043A\u0442\u0458\u0430\u0431\u0440",
"\u043D\u043E\u0458\u0430\u0431\u0440",
"\u0434\u0435\u043A\u0430\u0431\u0440",
}
// monthNamesAzerbaijaniCyrillicAbbr lists the month name abbreviations in
// the Azerbaijani (Cyrillic).
monthNamesAzerbaijaniCyrillicAbbr = []string{
"\u0408\u0430\u043D",
"\u0424\u0435\u0432",
"\u041C\u0430\u0440",
"\u0410\u043F\u0440",
"\u041C\u0430\u0458",
"\u0418\u0458\u0443\u043D",
"\u0418\u0458\u0443\u043B",
"\u0410\u0432\u0433",
"\u0421\u0435\u043D",
"\u041E\u043A\u0442",
"\u041D\u043E\u044F",
"\u0414\u0435\u043A",
}
// monthNamesAzerbaijani list the month names in the Azerbaijani.
monthNamesAzerbaijani = []string{"yanvar", "fevral", "mart", "aprel", "may", "iyun", "iyul", "avgust", "sentyabr", "oktyabr", "noyabr", "dekabr"}
// monthNamesAzerbaijaniAbbr lists the month name abbreviations in the
// Azerbaijani.
monthNamesAzerbaijaniAbbr = []string{"yan", "fev", "mar", "apr", "may", "iyn", "iyl", "avq", "sen", "okt", "noy", "dek"}
// monthNamesAustria list the month names in the Austrian.
monthNamesAustria = []string{"Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
// monthNamesAustriaAbbr list the month name abbreviations in the Austrian.
monthNamesAustriaAbbr = []string{"Jän", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
// monthNamesBangla list the month names in the Bangla.
monthNamesBangla = []string{
"\u099C\u09BE\u09A8\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0",
"\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0",
"\u09AE\u09BE\u09B0\u09CD\u099A",
"\u098F\u09AA\u09CD\u09B0\u09BF\u09B2",
"\u09AE\u09C7",
"\u099C\u09C1\u09A8",
"\u099C\u09C1\u09B2\u09BE\u0987",
"\u0986\u0997\u09B8\u09CD\u099F",
"\u09B8\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09B0",
"\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09B0",
"\u09A8\u09AD\u09C7\u09AE\u09CD\u09AC\u09B0",
"\u09A1\u09BF\u09B8\u09C7\u09AE\u09CD\u09AC\u09B0",
}
// monthNamesBashkir list the month names in the Bashkir.
monthNamesBashkir = []string{
"\u0493\u0438\u043D\u0443\u0430\u0440",
"\u0444\u0435\u0432\u0440\u0430\u043B\u044C",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0435\u043B\u044C",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D\u044C",
"\u0438\u044E\u043B\u044C",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C",
"\u043E\u043A\u0442\u044F\u0431\u0440\u044C",
"\u043D\u043E\u044F\u0431\u0440\u044C",
"\u0434\u0435\u043A\u0430\u0431\u0440\u044C",
}
// monthNamesBashkirAbbr lists the month name abbreviations in the Bashkir.
monthNamesBashkirAbbr = []string{
"\u0493\u0438\u043D",
"\u0444\u0435\u0432",
"\u043C\u0430\u0440",
"\u0430\u043F\u0440",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D",
"\u0438\u044E\u043B",
"\u0430\u0432\u0433",
"\u0441\u0435\u043D",
"\u043E\u043A\u0442",
"\u043D\u043E\u044F",
"\u0434\u0435\u043A",
}
// monthNamesBasque list the month names in the Basque.
monthNamesBasque = []string{"urtarrila", "otsaila", "martxoa", "apirila", "maiatza", "ekaina", "uztaila", "abuztua", "iraila", "urria", "azaroa", "abendua"}
// monthNamesBasqueAbbr lists the month name abbreviations in the Basque.
monthNamesBasqueAbbr = []string{"urt.", "ots.", "mar.", "api.", "mai.", "eka.", "uzt.", "abu.", "ira.", "urr.", "aza.", "abe."}
// monthNamesBelarusian list the month names in the Belarusian.
monthNamesBelarusian = []string{
"\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C",
"\u043B\u044E\u0442\u044B",
"\u0441\u0430\u043A\u0430\u0432\u0456\u043A",
"\u043A\u0440\u0430\u0441\u0430\u0432\u0456\u043A",
"\u043C\u0430\u0439",
"\u0447\u044D\u0440\u0432\u0435\u043D\u044C",
"\u043B\u0456\u043F\u0435\u043D\u044C",
"\u0436\u043D\u0456\u0432\u0435\u043D\u044C",
"\u0432\u0435\u0440\u0430\u0441\u0435\u043D\u044C",
"\u043A\u0430\u0441\u0442\u0440\u044B\u0447\u043D\u0456\u043A",
"\u043B\u0456\u0441\u0442\u0430\u043F\u0430\u0434",
"\u0441\u043D\u0435\u0436\u0430\u043D\u044C",
}
// monthNamesBelarusianAbbr lists the month name abbreviations in the
// Belarusian.
monthNamesBelarusianAbbr = []string{
"\u0441\u0442\u0443\u0434\u0437",
"\u043B\u044E\u0442",
"\u0441\u0430\u043A",
"\u043A\u0440\u0430\u0441",
"\u043C\u0430\u0439",
"\u0447\u044D\u0440\u0432",
"\u043B\u0456\u043F",
"\u0436\u043D",
"\u0432\u0435\u0440",
"\u043A\u0430\u0441\u0442\u0440",
"\u043B\u0456\u0441\u0442",
"\u0441\u043D\u0435\u0436",
}
// monthNamesBosnianCyrillic list the month names in the Bosnian (Cyrillic).
monthNamesBosnianCyrillic = []string{
"\u0458\u0430\u043D\u0443\u0430\u0440",
"\u0444\u0435\u0431\u0440\u0443\u0430\u0440",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0438\u043B",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D",
"\u0458\u0443\u043B",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440",
"\u043E\u043A\u0442\u043E\u0431\u0430\u0440",
"\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440",
"\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440",
}
// monthNamesBosnianCyrillicAbbr lists the month name abbreviations in the
// Bosnian (Cyrillic).
monthNamesBosnianCyrillicAbbr = []string{
"\u0458\u0430\u043D",
"\u0444\u0435\u0431",
"\u043C\u0430\u0440",
"\u0430\u043F\u0440",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D",
"\u0458\u0443\u043B",
"\u0430\u0432\u0433",
"\u0441\u0435\u043F",
"\u043E\u043A\u0442",
"\u043D\u043E\u0432",
"\u0434\u0435\u0446",
}
// monthNamesBosnian list the month names in the Bosnian.
monthNamesBosnian = []string{"januar", "februar", "mart", "april", "maj", "juni", "juli", "august", "septembar", "oktobar", "novembar", "decembar"}
// monthNamesBosnianAbbr lists the month name abbreviations in the Bosnian.
monthNamesBosnianAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}
// monthNamesBreton list the month names in the Breton.
monthNamesBreton = []string{"Genver", "C\u02bchwevrer", "Meurzh", "Ebrel", "Mae", "Mezheven", "Gouere", "Eost", "Gwengolo", "Here", "Du", "Kerzu"}
// monthNamesBretonAbbr lists the month name abbreviations in the Breton.
monthNamesBretonAbbr = []string{"Gen.", "C\u02bchwe.", "Meur.", "Ebr.", "Mae", "Mezh.", "Goue.", "Eost", "Gwen.", "Here", "Du", "Kzu."}
// monthNamesBulgarian list the month names in the Bulgarian.
monthNamesBulgarian = []string{
"\u044F\u043D\u0443\u0430\u0440\u0438",
"\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0438\u043B",
"\u043C\u0430\u0439",
"\u044E\u043D\u0438",
"\u044E\u043B\u0438",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043F\u0442\u0435\u043C\u0432\u0440\u0438",
"\u043E\u043A\u0442\u043E\u043C\u0432\u0440\u0438",
"\u043D\u043E\u0435\u043C\u0432\u0440\u0438",
"\u0434\u0435\u043A\u0435\u043C\u0432\u0440\u0438",
}
// monthNamesBurmese list the month names in the Burmese.
monthNamesBurmese = []string{
"\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E",
"\u1016\u1031\u1016\u1031\u102C\u103A\u101D\u102B\u101B\u102E",
"\u1019\u1010\u103A",
"\u1027\u1015\u103C\u102E",
"\u1019\u1031",
"\u1007\u103D\u1014\u103A",
"\u1007\u1030\u101C\u102D\u102F\u1004\u103A",
"\u1029\u1002\u102F\u1010\u103A",
"\u1005\u1000\u103A\u1010\u1004\u103A\u1018\u102C",
"\u1021\u1031\u102C\u1000\u103A\u1010\u102D\u102F\u1018\u102C",
"\u1014\u102D\u102F\u101D\u1004\u103A\u1018\u102C",
"\u1012\u102E\u1007\u1004\u103A\u1018\u102C",
}
// monthNamesBurmeseAbbr lists the month name abbreviations in the Burmese.
monthNamesBurmeseAbbr = []string{
"\u1007\u1014\u103A",
"\u1016\u1031",
"\u1019\u1010\u103A",
"\u1027",
"\u1019\u1031",
"\u1007\u103D\u1014\u103A",
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103A",
"\u1021\u1031\u102C\u1000\u103A",
"\u1014\u102D\u102F",
"\u1012\u102E",
}
// monthNamesCaribbean list the month names in the Caribbean.
monthNamesCaribbean = []string{"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"}
// monthNamesCaribbeanAbbr lists the month name abbreviations in the
// Caribbean.
monthNamesCaribbeanAbbr = []string{"Janv.", "Févr.", "Mars", "Avr.", "Mai", "Juin", "Juil.", "Août", "Sept.", "Oct.", "Nov.", "Déc."}
// monthNamesCentralKurdish list the month names in the Central Kurdish.
monthNamesCentralKurdish = []string{
"\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645",
"\u0634\u0648\u0628\u0627\u062A",
"\u0626\u0627\u0632\u0627\u0631",
"\u0646\u06CC\u0633\u0627\u0646",
"\u0626\u0627\u06CC\u0627\u0631",
"\u062D\u0648\u0632\u06D5\u06CC\u0631\u0627\u0646",
"\u062A\u06D5\u0645\u0648\u0648\u0632",
"\u0626\u0627\u0628",
"\u0626\u06D5\u06CC\u0644\u0648\u0648\u0644",
"\u062A\u0634\u0631\u06CC\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645",
"\u062A\u0634\u0631\u06CC\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645",
"\u06A9\u0627\u0646\u0648\u0646\u06CC \u06CC\u06D5\u06A9\u06D5\u0645",
}
// monthNamesCherokee list the month names in the Cherokee.
monthNamesCherokee = []string{
"\u13A4\u13C3\u13B8\u13D4\u13C5",
"\u13A7\u13A6\u13B5",
"\u13A0\u13C5\u13F1",
"\u13DD\u13EC\u13C2",
"\u13A0\u13C2\u13CD\u13AC\u13D8",
"\u13D5\u13AD\u13B7\u13F1",
"\u13AB\u13F0\u13C9\u13C2",
"\u13A6\u13B6\u13C2",
"\u13DA\u13B5\u13CD\u13D7",
"\u13DA\u13C2\u13C5\u13D7",
"\u13C5\u13D3\u13D5\u13C6",
"\u13A4\u13CD\u13A9\u13F1",
}
// monthNamesCherokeeAbbr lists the month name abbreviations in the
// Cherokee.
monthNamesCherokeeAbbr = []string{
"\u13A4\u13C3\u13B8",
"\u13A7\u13A6\u13B5",
"\u13A0\u13C5\u13F1",
"\u13DD\u13EC\u13C2",
"\u13A0\u13C2\u13CD",
"\u13D5\u13AD\u13B7",
"\u13AB\u13F0\u13C9",
"\u13A6\u13B6\u13C2",
"\u13DA\u13B5\u13CD",
"\u13DA\u13C2\u13C5",
"\u13C5\u13D3\u13D5",
"\u13A4\u13CD\u13A9",
}
// monthNamesChinese list the month names in the Chinese.
monthNamesChinese = []string{"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"}
// monthNamesChineseAbbr lists the month name abbreviations in the Chinese.
monthNamesChineseAbbr = []string{"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}
// monthNamesChineseNum list the month number and character abbreviation in
// the Chinese.
monthNamesChineseNum = []string{"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"}
// monthNamesCorsican list the month names in the Corsican.
monthNamesCorsican = []string{"ghjennaghju", "ferraghju", "marzu", "aprile", "maghju", "ghjunghju", "lugliu", "aostu", "settembre", "ottobre", "nuvembre", "dicembre"}
// monthNamesCorsican lists the month name abbreviations in the Corsican.
monthNamesCorsicanAbbr = []string{"ghje", "ferr", "marz", "apri", "magh", "ghju", "lugl", "aost", "sett", "otto", "nuve", "dice"}
// monthNamesCroatian list the month names in the Croatian.
monthNamesCroatian = []string{"siječanj", "veljača", "ožujak", "travanj", "svibanj", "lipanj", "srpanj", "kolovoz", "rujan", "listopad", "studeni", "prosinac"}
// monthNamesCroatian lists the month name abbreviations in the Croatian.
monthNamesCroatianAbbr = []string{"sij", "vlj", "ožu", "tra", "svi", "lip", "srp", "kol", "ruj", "lis", "stu", "pro"}
// monthNamesCzech list the month names in the Czech.
monthNamesCzech = []string{"leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec"}
// monthNamesCzech lists the month name abbreviations in the Czech.
monthNamesCzechAbbr = []string{"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"}
// monthNamesDanish list the month names in the Danish.
monthNamesDanish = []string{"januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december"}
// monthNamesDanish lists the month name abbreviations in the Danish.
monthNamesDanishAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}
// monthNamesDari list the month names in the Dari.
monthNamesDari = []string{
"\u062C\u0646\u0648\u0631\u06CC",
"\u0641\u0628\u0631\u0648\u0631\u06CC",
"\u0645\u0627\u0631\u0686",
"\u0627\u067E\u0631\u06CC\u0644",
"\u0645\u06CC",
"\u062C\u0648\u0646",
"\u062C\u0648\u0644\u0627\u06CC",
"\u0627\u06AF\u0633\u062A",
"\u0633\u067E\u062A\u0645\u0628\u0631",
"\u0627\u06A9\u062A\u0648\u0628\u0631",
"\u0646\u0648\u0645\u0628\u0631",
"\u062F\u0633\u0645\u0628\u0631",
}
// monthNamesDariAbbr lists the month name abbreviations in the Dari.
monthNamesDariAbbr = []string{
"\u062C\u062F\u06CC",
"\u062F\u0644\u0648",
"\u062D\u0648\u062A",
"\u062D\u0645\u0644",
"\u062B\u0648\u0631",
"\u062C\u0648\u0632\u0627",
"\u0633\u0631\u0637\u0627\u0646",
"\u0627\u0633\u062F",
"\u0633\u0646\u0628\u0644\u0647",
"\u0645\u06CC\u0632\u0627\u0646",
"\u0639\u0642\u0631\u0628",
"\u0642\u0648\u0633",
}
// monthNamesDivehi lists the month names in the Divehi.
monthNamesDivehi = []string{
"\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9",
"\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9",
"\u0789\u07A7\u0783\u0797\u07B0",
"\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0",
"\u0789\u07AC\u0787\u07A8",
"\u0796\u07AB\u0782\u07B0",
"\u0796\u07AA\u078D\u07A6\u0787\u07A8",
"\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0",
"\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783",
"\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783",
"\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783",
"\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783",
}
// monthNamesDutch list the month names in the Dutch.
monthNamesDutch = []string{"januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"}
// monthNamesDutch lists the month name abbreviations in the Dutch.
monthNamesDutchAbbr = []string{"jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}
// monthNamesDzongkha list the month names in the Dzongkha.
monthNamesDzongkha = []string{
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F44\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B",
}
// monthNamesDzongkhaAbbr lists the month name abbreviations in the
// Dzongkha.
monthNamesDzongkhaAbbr = []string{
"\u0F5F\u0FB3\u0F0B\u0F21",
"\u0F5F\u0FB3\u0F0B\u0F22",
"\u0F5F\u0FB3\u0F0B\u0F23",
"\u0F5F\u0FB3\u0F0B\u0F24",
"\u0F5F\u0FB3\u0F0B\u0F25",
"\u0F5F\u0FB3\u0F0B\u0F26",
"\u0F5F\u0FB3\u0F0B\u0F27",
"\u0F5F\u0FB3\u0F0B\u0F28",
"\u0F5F\u0FB3\u0F0B\u0F29",
"\u0F5F\u0FB3\u0F0B\u0F21\u0F20",
"\u0F5F\u0FB3\u0F0B\u0F21\u0F21",
"\u0F5F\u0FB3\u0F0B\u0F21\u0F22",
}
// monthNamesEstonian list the month names in the Estonian.
monthNamesEstonian = []string{"jaanuar", "veebruar", "märts", "aprill", "mai", "juuni", "juuli", "august", "september", "oktoober", "november", "detsember"}
// monthNamesEstonianAbbr lists the month name abbreviations in the
// Estonian.
monthNamesEstonianAbbr = []string{"jaan", "veebr", "märts", "apr", "mai", "juuni", "juuli", "aug", "sept", "okt", "nov", "dets"}
// monthNamesFaroese list the month names in the Faroese.
monthNamesFaroese = []string{"januar", "februar", "mars", "apríl", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember"}
// monthsNameFaroeseAbbr lists the month name abbreviations in the Faroese.
monthsNameFaroeseAbbr = []string{"jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "des"}
// monthNamesFilipino list the month names in the Filipino.
monthNamesFilipino = []string{"Enero", "Pebrero", "Marso", "Abril", "Mayo", "Hunyo", "Hulyo", "Agosto", "Setyembre", "Oktubre", "Nobyembre", "Disyembre"}
// monthNamesFilipinoAbbr lists the month name abbreviations in the
// Filipino.
monthNamesFilipinoAbbr = []string{"Ene", "Peb", "Mar", "Abr", "May", "Hun", "Hul", "Ago", "Set", "Okt", "Nob", "Dis"}
// monthsNamesFinnish list the month names in the Finnish.
monthNamesFinnish = []string{"Etammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"}
// monthsNamesFinnishAbbr lists the month name abbreviations in the Finnish.
monthNamesFinnishAbbr = []string{"tammi", "helmi", "maalis", "huhti", "touko", "kesä", "heinä", "elo", "syys", "loka", "marras", "joulu"}
// monthNamesFrench list the month names in the French.
monthNamesFrench = []string{"janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"}
// monthNamesFrenchAbbr lists the month name abbreviations in the French.
monthNamesFrenchAbbr = []string{"janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."}
// monthNamesFrisian list the month names in the Frisian.
monthNamesFrisian = []string{"Jannewaris", "Febrewaris", "Maart", "April", "Maaie", "Juny", "July", "Augustus", "Septimber", "Oktober", "Novimber", "Desimber"}
// monthNamesFrisianAbbr lists the month name abbreviations in the Frisian.
monthNamesFrisianAbbr = []string{"jan", "feb", "Mrt", "Apr", "maa", "Jun", "Jul", "Aug", "sep", "Okt", "Nov", "Des"}
// monthNamesFulah list the month names in the Fulah.
monthNamesFulah = []string{"siilo", "colte", "mbooy", "seeɗto", "duujal", "korse", "morso", "juko", "siilto", "yarkomaa", "jolal", "bowte"}
// monthNamesFulahAbbr lists the month name abbreviations in the Fulah.
monthNamesFulahAbbr = []string{"sii", "col", "mbo", "see", "duu", "kor", "mor", "juk", "slt", "yar", "jol", "bow"}
// monthNamesGalician list the month names in the Galician.
monthNamesGalician = []string{"Xaneiro", "Febreiro", "Marzo", "Abril", "Maio", "Xuño", "Xullo", "Agosto", "Setembro", "Outubro", "Novembro", "Decembro"}
// monthNamesGalicianAbbr lists the month name abbreviations in the
// Galician.
monthNamesGalicianAbbr = []string{"Xan.", "Feb.", "Mar.", "Abr.", "Maio", "Xuño", "Xul.", "Ago.", "Set.", "Out.", "Nov.", "Dec."}
// monthNamesGeorgian list the month names in the Georgian.
monthNamesGeorgian = []string{
"\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8",
"\u10D7\u10D4\u10D1\u10D4\u10E0\u10D5\u10D0\u10DA\u10D8",
"\u10DB\u10D0\u10E0\u10E2\u10D8",
"\u10D0\u10DE\u10E0\u10D8\u10DA\u10D8",
"\u10DB\u10D0\u10D8\u10E1\u10D8",
"\u10D8\u10D5\u10DC\u10D8\u10E1\u10D8",
"\u10D8\u10D5\u10DA\u10D8\u10E1\u10D8",
"\u10D0\u10D2\u10D5\u10D8\u10E1\u10E2\u10DD",
"\u10E1\u10D4\u10E5\u10E2\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8",
"\u10DD\u10E5\u10E2\u10DD\u10DB\u10D1\u10D4\u10E0\u10D8",
"\u10DC\u10DD\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8",
"\u10D3\u10D4\u10D9\u10D4\u10DB\u10D1\u10D4\u10E0\u10D8",
}
// monthNamesGeorgianAbbr lists the month name abbreviations in the
// Georgian.
monthNamesGeorgianAbbr = []string{
"\u10D8\u10D0\u10DC",
"\u10D7\u10D4\u10D1",
"\u10DB\u10D0\u10E0",
"\u10D0\u10DE\u10E0",
"\u10DB\u10D0\u10D8",
"\u10D8\u10D5\u10DC",
"\u10D8\u10D5\u10DA",
"\u10D0\u10D2\u10D5",
"\u10E1\u10D4\u10E5",
"\u10DD\u10E5\u10E2",
"\u10DC\u10DD\u10D4",
"\u10D3\u10D4\u10D9",
}
// monthNamesGerman list the month names in the German.
monthNamesGerman = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
// monthNamesGermanAbbr list the month abbreviations in the German.
monthNamesGermanAbbr = []string{"Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
// monthNamesGreek list the month names in the Greek.
monthNamesGreek = []string{
"\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2",
"\u03A6\u03B5\u03B2\u03C1\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2",
"\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2",
"\u0391\u03C0\u03C1\u03AF\u03BB\u03B9\u03BF\u03C2",
"\u039C\u03AC\u03B9\u03BF\u03C2",
"\u0399\u03BF\u03CD\u03BD\u03B9\u03BF\u03C2",
"\u0399\u03BF\u03CD\u03BB\u03B9\u03BF\u03C2",
"\u0391\u03CD\u03B3\u03BF\u03C5\u03C3\u03C4\u03BF\u03C2",
"\u03A3\u03B5\u03C0\u03C4\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2",
"\u039F\u03BA\u03C4\u03CE\u03B2\u03C1\u03B9\u03BF\u03C2",
"\u039D\u03BF\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2",
"\u0394\u03B5\u03BA\u03AD\u03BC\u03B2\u03C1\u03B9\u03BF\u03C2",
}
// monthNamesGreekAbbr list the month abbreviations in the Greek.
monthNamesGreekAbbr = []string{
"\u0399\u03B1\u03BD",
"\u03A6\u03B5\u03B2",
"\u039C\u03B1\u03C1",
"\u0391\u03C0\u03C1",
"\u039C\u03B1\u03CA",
"\u0399\u03BF\u03C5\u03BD",
"\u0399\u03BF\u03C5\u03BB",
"\u0391\u03C5\u03B3",
"\u03A3\u03B5\u03C0",
"\u039F\u03BA\u03C4",
"\u039D\u03BF\u03B5",
"\u0394\u03B5\u03BA",
}
// monthNamesGreenlandic list the month names in the Greenlandic.
monthNamesGreenlandic = []string{"januaari", "februaari", "marsi", "apriili", "maaji", "juuni", "juuli", "aggusti", "septembari", "oktobari", "novembari", "decembari"}
// monthNamesGreenlandicAbbr list the month abbreviations in the
// Greenlandic.
monthNamesGreenlandicAbbr = []string{"jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}
// monthNamesGuarani list the month names in the Guarani.
monthNamesGuarani = []string{"jasyte\u0129", "jasykõi", "jasyapy", "jasyrundy", "jasypo", "jasypote\u0129", "jasypokõi", "jasypoapy", "jasyporundy", "jasypa", "jasypate\u0129", "jasypakõi"}
// monthNamesGuaraniAbbr list the month abbreviations in the Guarani.
monthNamesGuaraniAbbr = []string{"jteĩ", "jkõi", "japy", "jrun", "jpo", "jpot", "jpok", "jpoa", "jpor", "jpa", "jpat", "jpak"}
// monthNamesGujarati list the month names in the Gujarati.
monthNamesGujarati = []string{
"\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0",
"\u0AAB\u0AC7\u0AAC\u0ACD\u0AB0\u0AC1\u0A86\u0AB0\u0AC0",
"\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A",
"\u0A8F\u0AAA\u0ACD\u0AB0\u0ABF\u0AB2",
"\u0AAE\u0AC7",
"\u0A9C\u0AC2\u0AA8",
"\u0A9C\u0AC1\u0AB2\u0ABE\u0A88",
"\u0A91\u0A97\u0AB8\u0ACD\u0A9F",
"\u0AB8\u0AAA\u0ACD\u0A9F\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0",
"\u0A91\u0A95\u0ACD\u0A9F\u0ACB\u0AAC\u0AB0",
"\u0AA8\u0AB5\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0",
"\u0AA1\u0ABF\u0AB8\u0AC7\u0AAE\u0ACD\u0AAC\u0AB0",
}
// monthNamesGujaratiAbbr list the month abbreviations in the Gujarati.
monthNamesGujaratiAbbr = []string{
"\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1",
"\u0AAB\u0AC7\u0AAC\u0ACD\u0AB0\u0AC1",
"\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A",
"\u0A8F\u0AAA\u0ACD\u0AB0\u0ABF\u0AB2",
"\u0AAE\u0AC7",
"\u0A9C\u0AC2\u0AA8",
"\u0A9C\u0AC1\u0AB2\u0ABE\u0A88",
"\u0A91\u0A97",
"\u0AB8\u0AAA\u0ACD\u0A9F\u0AC7",
"\u0A91\u0A95\u0ACD\u0A9F\u0ACB",
"\u0AA8\u0AB5\u0AC7",
"\u0AA1\u0ABF\u0AB8\u0AC7",
}
// monthNamesHausa list the month names in the Hausa.
monthNamesHausa = []string{"Janairu", "Fabrairu", "Maris", "Afirilu", "Mayu", "Yuni", "Yuli", "Agusta", "Satumba", "Oktoba", "Nuwamba", "Disamba"}
// monthNamesHawaiian list the month names in the Hawaiian.
monthNamesHawaiian = []string{"Ianuali", "Pepeluali", "Malaki", "\u02bbApelila", "Mei", "Iune", "Iulai", "\u02bbAukake", "Kepakemapa", "\u02bbOkakopa", "Nowemapa", "Kekemapa"}
// monthNamesHawaiianAbbr list the month name abbreviations in the
// Hawaiiann.
monthNamesHawaiianAbbr = []string{"Ian.", "Pep.", "Mal.", "\u02bbAp.", "Mei", "Iun.", "Iul.", "\u02bbAu.", "Kep.", "\u02bbOk.", "Now.", "Kek."}
// monthNamesHebrew list the month names in the Hebrew.
monthNamesHebrew = []string{
"\u05D9\u05E0\u05D5\u05D0\u05E8",
"\u05E4\u05D1\u05E8\u05D5\u05D0\u05E8",
"\u05DE\u05E8\u05E5",
"\u05D0\u05E4\u05E8\u05D9\u05DC",
"\u05DE\u05D0\u05D9",
"\u05D9\u05D5\u05E0\u05D9",
"\u05D9\u05D5\u05DC\u05D9",
"\u05D0\u05D5\u05D2\u05D5\u05E1\u05D8",
"\u05E1\u05E4\u05D8\u05DE\u05D1\u05E8",
"\u05D0\u05D5\u05E7\u05D8\u05D5\u05D1\u05E8",
"\u05E0\u05D5\u05D1\u05DE\u05D1\u05E8",
"\u05D3\u05E6\u05DE\u05D1\u05E8",
}
// monthNamesHindi list the month names in the Hindi.
monthNamesHindi = []string{
"\u091C\u0928\u0935\u0930\u0940",
"\u092B\u0930\u0935\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u0905\u092A\u094D\u0930\u0948\u0932",
"\u092E\u0908",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u093E\u0908",
"\u0905\u0917\u0938\u094D\u0924",
"\u0938\u093F\u0924\u092E\u094D\u092C\u0930",
"\u0905\u0915\u094D\u0924\u0942\u092C\u0930",
"\u0928\u0935\u092E\u094D\u092C\u0930",
"\u0926\u093F\u0938\u092E\u094D\u092C\u0930",
}
// monthNamesHungarian list the month names in the Hungarian.
monthNamesHungarian = []string{"január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december"}
// monthNamesHungarianAbbr list the month name abbreviations in the
// Hungarian.
monthNamesHungarianAbbr = []string{"jan.", "febr.", "márc.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec."}
// monthNamesIcelandic list the month names in the Icelandic.
monthNamesIcelandic = []string{"janúar", "febrúar", "mars", "apríl", "maí", "júní", "júlí", "ágúst", "september", "október", "nóvember", "desember"}
// monthNamesIcelandicAbbr list the month name abbreviations in the
// Icelandic.
monthNamesIcelandicAbbr = []string{"jan.", "feb.", "mar.", "apr.", "maí", "jún.", "júl.", "ágú.", "sep.", "okt.", "nóv.", "des."}
// monthNamesIgbo list the month names in the Igbo.
monthNamesIgbo = []string{"Jenụwarị", "Febụwarị", "Machị", "Eprelu", "Mey", "Juun", "Julaị", "Ọgọst", "Septemba", "Ọcktọba", "Nọvemba", "Disemba"}
// monthNamesIgboAbbr list the month name abbreviations in the Igbo.
monthNamesIgboAbbr = []string{"Jen", "Feb", "Mac", "Epr", "Mey", "Jun", "Jul", "Ọgọ", "Sep", "Ọkt", "Nọv", "Dis"}
// monthNamesIndonesian list the month names in the Indonesian.
monthNamesIndonesian = []string{"Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"}
// monthNamesIndonesianAbbr list the month name abbreviations in the
// Indonesian.
monthNamesIndonesianAbbr = []string{"Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"}
// monthNamesInuktitut list the month names in the Inuktitut.
monthNamesInuktitut = []string{"Jaannuari", "Viivvuari", "Maatsi", "Iipuri", "Mai", "Juuni", "Julai", "Aaggiisi", "Sitipiri", "Utupiri", "Nuvipiri", "Tisipiri"}
// monthNamesInuktitutAbbr list the month name abbreviations in the
// Inuktitut.
monthNamesInuktitutAbbr = []string{"Jan", "Viv", "Mas", "Ipu", "Mai", "Jun", "Jul", "Agi", "Sii", "Uut", "Nuv", "Tis"}
// monthNamesIrish list the month names in the Irish.
monthNamesIrish = []string{"Eanáir", "Feabhra", "Márta", "Aibreán", "Bealtaine", "Meitheamh", "Iúil", "Lúnasa", "Meán Fómhair", "Deireadh Fómhair", "Samhain", "Nollaig"}
// monthNamesIrishAbbr lists the month abbreviations in the Irish.
monthNamesIrishAbbr = []string{"Ean", "Feabh", "Márta", "Aib", "Beal", "Meith", "Iúil", "Lún", "MFómh", "DFómh", "Samh", "Noll"}
// monthNamesItalian list the month names in the Italian.
monthNamesItalian = []string{"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"}
// monthNamesItalianAbbr list the month name abbreviations in the Italian.
monthNamesItalianAbbr = []string{"gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic"}
// monthNamesKannada list the month names in the Kannada.
monthNamesKannada = []string{
"\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF",
"\u0CAB\u0CC6\u0CAC\u0CCD\u0CB0\u0CB5\u0CB0\u0CBF",
"\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD",
"\u0C8F\u0C8F\u0CAA\u0CCD\u0CB0\u0CBF\u0CB2\u0CCD",
"\u0CAE\u0CC7",
"\u0C9C\u0CC2\u0CA8\u0CCD",
"\u0C9C\u0CC1\u0CB2\u0CC8",
"\u0C86\u0C97\u0CB8\u0CCD\u0C9F\u0CCD",
"\u0CB8\u0CC6\u0CAA\u0CCD\u0C9F\u0C82\u0CAC\u0CB0\u0CCD",
"\u0C85\u0C95\u0CCD\u0C9F\u0CCB\u0CAC\u0CB0\u0CCD",
"\u0CA8\u0CB5\u0CC6\u0C82\u0CAC\u0CB0\u0CCD",
"\u0CA1\u0CBF\u0CB8\u0CC6\u0C82\u0CAC\u0CB0\u0CCD",
}
// monthNamesKannadaAbbr lists the month abbreviations in the Kannada.
monthNamesKannadaAbbr = []string{
"\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF",
"\u0CAB\u0CC6\u0CAC\u0CCD\u0CB0\u0CB5\u0CB0\u0CBF",
"\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD",
"\u0C8E\u0CAA\u0CCD\u0CB0\u0CBF\u0CB2\u0CCD",
"\u0CAE\u0CC7",
"\u0C9C\u0CC2\u0CA8\u0CCD",
"\u0C9C\u0CC1\u0CB2\u0CC8",
"\u0C86\u0C97\u0CB8\u0CCD\u0C9F\u0CCD",
"\u0CB8\u0CC6\u0CAA\u0CCD\u0C9F\u0C82\u0CAC\u0CB0\u0CCD",
"\u0C85\u0C95\u0CCD\u0C9F\u0CCB\u0CAC\u0CB0\u0CCD",
"\u0CA8\u0CB5\u0CC6\u0C82\u0CAC\u0CB0\u0CCD",
"\u0CA1\u0CBF\u0CB8\u0CC6\u0C82\u0CAC\u0CB0\u0CCD",
}
// monthNamesKashmiri list the month names in the Kashmiri.
monthNamesKashmiri = []string{
"\u062C\u0646\u0624\u0631\u06CC",
"\u0641\u0631\u0624\u0631\u06CC",
"\u0645\u0627\u0631\u0655\u0686",
"\u0627\u067E\u0631\u06CC\u0644",
"\u0645\u06CC\u0654",
"\u062C\u0648\u0657\u0646",
"\u062C\u0648\u0657\u0644\u0627\u06CC\u06CC",
"\u0627\u06AF\u0633\u062A",
"\u0633\u062A\u0645\u0628\u0631",
"\u0627\u06A9\u062A\u0648\u0657\u0628\u0631",
"\u0646\u0648\u0645\u0628\u0631",
"\u062F\u0633\u0645\u0628\u0631",
}
// monthNamesKazakh list the month names in the Kazakh.
monthNamesKazakh = []string{
"\u049A\u0430\u04A3\u0442\u0430\u0440",
"\u0410\u049B\u043F\u0430\u043D",
"\u041D\u0430\u0443\u0440\u044B\u0437",
"\u0421\u04D9\u0443\u0456\u0440",
"\u041C\u0430\u043C\u044B\u0440",
"\u041C\u0430\u0443\u0441\u044B\u043C",
"\u0428\u0456\u043B\u0434\u0435",
"\u0422\u0430\u043C\u044B\u0437",
"\u049A\u044B\u0440\u043A\u04AF\u0439\u0435\u043A",
"\u049A\u0430\u0437\u0430\u043D",
"\u049A\u0430\u0440\u0430\u0448\u0430",
"\u0416\u0435\u043B\u0442\u043E\u049B\u0441\u0430\u043D",
}
// monthNamesKazakhAbbr list the month name abbreviations in the Kazakh.
monthNamesKazakhAbbr = []string{
"\u049B\u0430\u04A3",
"\u0430\u049B\u043F",
"\u043D\u0430\u0443",
"\u0441\u04D9\u0443",
"\u043C\u0430\u043C",
"\u043C\u0430\u0443",
"\u0448\u0456\u043B",
"\u0442\u0430\u043C",
"\u049B\u044B\u0440",
"\u049B\u0430\u0437",
"\u049B\u0430\u0440",
"\u0436\u0435\u043B",
}
// monthNamesKhmer list the month names in the Khmer.
monthNamesKhmer = []string{
"\u1798\u1780\u179A\u17B6",
"\u1780\u17BB\u1798\u17D2\u1797\u17C8",
"\u1798\u17B7\u1793\u17B6",
"\u1798\u17C1\u179F\u17B6",
"\u17A7\u179F\u1797\u17B6",
"\u1798\u17B7\u1790\u17BB\u1793\u17B6",
"\u1780\u1780\u17D2\u1780\u178A\u17B6",
"\u179F\u17B8\u17A0\u17B6",
"\u1780\u1789\u17D2\u1789\u17B6",
"\u178F\u17BB\u179B\u17B6",
"\u179C\u17B7\u1785\u17D2\u1786\u17B7\u1780\u17B6",
"\u1792\u17D2\u1793\u17BC",
}
// monthNamesKhmerAbbr list the month name abbreviations in the Khmer.
monthNamesKhmerAbbr = []string{
"\u17E1", "\u17E2", "\u17E3", "\u17E4", "\u17E5", "\u17E6", "\u17E7", "\u17E8", "\u17E9", "\u17E1\u17E0", "\u17E1\u17E1", "\u17E1\u17E2",
"\u1798", "\u1780", "\u1798", "\u1798", "\u17A7", "\u1798", "\u1780", "\u179F", "\u1780", "\u178F", "\u179C", "\u1792",
}
// monthNamesKiche list the month names in the Kiche.
monthNamesKiche = []string{"nab'e ik'", "ukab' ik'", "urox ik'", "ukaj ik'", "uro ik'", "uwaq ik'", "uwuq ik'", "uwajxaq ik'", "ub'elej ik'", "ulaj ik'", "ujulaj ik'", "ukab'laj ik'"}
// monthNamesKicheAbbr list the month name abbreviations in the Kiche.
monthNamesKicheAbbr = []string{"nab'e", "ukab'", "urox", "ukaj", "uro", "uwaq", "uwuq", "uwajxaq", "ub'elej", "ulaj", "ujulaj", "ukab'laj"}
// monthNamesKinyarwanda list the month names in the Kinyarwanda.
monthNamesKinyarwanda = []string{"Mutarama", "Gashyantare", "Werurwe", "Mata", "Gicuransi", "Kamena", "Nyakanga", "Kanama", "Nzeli", "Ukwakira", "Ugushyingo", "Ukuboza"}
// monthNamesKinyarwandaAbbr list the month name abbreviations in the Kinyarwanda.
monthNamesKinyarwandaAbbr = []string{"mut.", "gas.", "wer.", "mat.", "gic.", "kam.", "Nyak", "kan.", "nze.", "Ukwak", "Ugus", "Ukub"}
// monthNamesKiswahili list the month names in the Kiswahili.
monthNamesKiswahili = []string{"Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba"}
// monthNamesKiswahiliAbbr list the month name abbreviations in the Kiswahili.
monthNamesKiswahiliAbbr = []string{"Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des"}
// monthNamesKonkani list the month names in the Konkani.
monthNamesKonkani = []string{
"\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940",
"\u092B\u0947\u092C\u094D\u0930\u0941\u0935\u093E\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u090F\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u0948",
"\u0911\u0917\u0938\u094D\u091F",
"\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930",
"\u0911\u0915\u094D\u091F\u094B\u092C\u0930",
"\u0928\u094B\u0935\u0947\u092E\u094D\u092C\u0930",
"\u0921\u093F\u0938\u0947\u0902\u092C\u0930",
}
// monthNamesKonkaniAbbr list the month name abbreviations in the Konkani.
monthNamesKonkaniAbbr = []string{
"\u091C\u093E\u0928\u0947",
"\u092B\u0947\u092C\u094D\u0930\u0941",
"\u092E\u093E\u0930\u094D\u091A",
"\u090F\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u0948",
"\u0911\u0917.",
"\u0938\u092A\u094D\u091F\u0947\u0902.",
"\u0911\u0915\u094D\u091F\u094B.",
"\u0928\u094B\u0935\u0947.",
"\u0921\u093F\u0938\u0947\u0902",
}
// monthNamesKoreanAbbr lists out the month number plus 월 for the Korean language.
monthNamesKoreanAbbr = []string{"1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"}
// monthNamesKyrgyz list the month names in the Kyrgyz.
monthNamesKyrgyz = []string{
"\u042F\u043D\u0432\u0430\u0440\u044C",
"\u0424\u0435\u0432\u0440\u0430\u043B\u044C",
"\u041C\u0430\u0440\u0442",
"\u0410\u043F\u0440\u0435\u043B\u044C",
"\u041C\u0430\u0439",
"\u0418\u044E\u043D\u044C",
"\u0418\u044E\u043B\u044C",
"\u0410\u0432\u0433\u0443\u0441\u0442",
"\u0421\u0435\u043D\u0442\u044F\u0431\u0440\u044C",
"\u041E\u043A\u0442\u044F\u0431\u0440\u044C",
"\u041D\u043E\u044F\u0431\u0440\u044C",
"\u0414\u0435\u043A\u0430\u0431\u0440\u044C",
}
// monthNamesKyrgyzAbbr lists the month name abbreviations in the Kyrgyz.
monthNamesKyrgyzAbbr = []string{
"\u042F\u043D\u0432",
"\u0424\u0435\u0432",
"\u041C\u0430\u0440",
"\u0410\u043F\u0440",
"\u041C\u0430\u0439",
"\u0418\u044E\u043D",
"\u0418\u044E\u043B",
"\u0410\u0432\u0433",
"\u0421\u0435\u043D",
"\u041E\u043A\u0442",
"\u041D\u043E\u044F",
"\u0414\u0435\u043A",
}
// monthNamesLao list the month names in the Lao.
monthNamesLao = []string{
"\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99",
"\u0E81\u0EB8\u0EA1\u0E9E\u0EB2",
"\u0EA1\u0EB5\u0E99\u0EB2",
"\u0EC0\u0EA1\u0EAA\u0EB2",
"\u0E9E\u0EB6\u0E94\u0EAA\u0EB0\u0E9E\u0EB2",
"\u0EA1\u0EB4\u0E96\u0EB8\u0E99\u0EB2",
"\u0E81\u0ECD\u0EA5\u0EB0\u0E81\u0EBB\u0E94",
"\u0EAA\u0EB4\u0E87\u0EAB\u0EB2",
"\u0E81\u0EB1\u0E99\u0E8D\u0EB2",
"\u0E95\u0EB8\u0EA5\u0EB2",
"\u0E9E\u0EB0\u0E88\u0EB4\u0E81",
"\u0E97\u0EB1\u0E99\u0EA7\u0EB2",
}
// monthNamesLaoAbbr lists the month name abbreviations in the Lao.
monthNamesLaoAbbr = []string{
"\u0EA1.\u0E81.",
"\u0E81.\u0E9E.",
"\u0EA1.\u0E99.",
"\u0EA1.\u0EAA.",
"\u0E9E.\u0E9E.",
"\u0EA1\u0EB4.\u0E96.",
"\u0E81.\u0EA5.",
"\u0EAA.\u0EAB.",
"\u0E81.\u0E8D.",
"\u0E95.\u0EA5.",
"\u0E9E.\u0E88.",
"\u0E97.\u0EA7.",
}
// monthNamesLatin list the month names in the Latin.
monthNamesLatin = []string{"Ianuarius", "Februarius", "Martius", "Aprilis", "Maius", "Iunius", "Quintilis", "Sextilis", "September", "October", "November", "December"}
// monthNamesLatinAbbr list the month name abbreviations in the Latin.
monthNamesLatinAbbr = []string{"Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Quint", "Sext", "Sept", "Oct", "Nov", "Dec"}
// monthNamesLatvian list the month names in the Latvian.
monthNamesLatvian = []string{"janvāris", "februāris", "marts", "aprīlis", "maijs", "jūnijs", "jūlijs", "augusts", "septembris", "oktobris", "novembris", "decembris"}
// monthNamesLatvianAbbr list the month name abbreviations in the Latvian.
monthNamesLatvianAbbr = []string{"janv.", "febr.", "marts", "apr.", "maijs", "jūn.", "jūl.", "aug.", "sept.", "okt.", "nov.", "dec."}
// monthNamesLithuanian list the month names in the Lithuanian.
monthNamesLithuanian = []string{"sausis", "vasaris", "kovas", "balandis", "gegužė", "birželis", "liepa", "rugpjūtis", "rugsėjis", "spalis", "lapkritis", "gruodis"}
// monthNamesLithuanianAbbr list the month name abbreviations in the Lithuanian.
monthNamesLithuanianAbbr = []string{"saus.", "vas.", "kov.", "bal.", "geg.", "birž.", "liep.", "rugp.", "rugs.", "spal.", "lapkr.", "gruod."}
// monthNamesLowerSorbian list the month names in the Lower Sorbian.
monthNamesLowerSorbian = []string{"januar", "februar", "měrc", "apryl", "maj", "junij", "julij", "awgust", "september", "oktober", "nowember", "december"}
// monthNamesLowerSorbianAbbr list the month name abbreviations in the LowerSorbian.
monthNamesLowerSorbianAbbr = []string{"jan", "feb", "měr", "apr", "maj", "jun", "jul", "awg", "sep", "okt", "now", "dec"}
// monthNamesLuxembourgish list the month names in the Lower Sorbian.
monthNamesLuxembourgish = []string{"Januar", "Februar", "Mäerz", "Abrëll", "Mee", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
// monthNamesLuxembourgishAbbr list the month name abbreviations in the Luxembourgish.
monthNamesLuxembourgishAbbr = []string{"Jan", "Feb", "Mäe", "Abr", "Mee", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
// monthNamesMacedonian list the month names in the Lower Sorbian.
monthNamesMacedonian = []string{
"\u0458\u0430\u043D\u0443\u0430\u0440\u0438",
"\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0438\u043B",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D\u0438",
"\u0458\u0443\u043B\u0438",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043F\u0442\u0435\u043C\u0432\u0440\u0438",
"\u043E\u043A\u0442\u043E\u043C\u0432\u0440\u0438",
"\u043D\u043E\u0435\u043C\u0432\u0440\u0438",
"\u0434\u0435\u043A\u0435\u043C\u0432\u0440\u0438",
}
// monthNamesMacedonianAbbr list the month name abbreviations in the Macedonian.
monthNamesMacedonianAbbr = []string{
"\u0458\u0430\u043D.",
"\u0444\u0435\u0432.",
"\u043C\u0430\u0440.",
"\u0430\u043F\u0440.",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D.",
"\u0458\u0443\u043B.",
"\u0430\u0432\u0433.",
"\u0441\u0435\u043F\u0442.",
"\u043E\u043A\u0442.",
"\u043D\u043E\u0435\u043C.",
"\u0434\u0435\u043A.",
}
// monthNamesMalay list the month names in the Malay.
monthNamesMalay = []string{"Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember"}
// monthNamesMalayAbbr list the month name abbreviations in the Malay.
monthNamesMalayAbbr = []string{"Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis"}
// monthNamesMalayalam list the month names in the Malayalam.
monthNamesMalayalam = []string{
"\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F",
"\u0D2B\u0D46\u0D2C\u0D4D\u0D30\u0D41\u0D35\u0D30\u0D3F",
"\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D",
"\u0D0F\u0D2A\u0D4D\u0D30\u0D3F\u0D32\u0D4D\u200D",
"\u0D2E\u0D47\u0D2F\u0D4D",
"\u0D1C\u0D42\u0D7A",
"\u0D1C\u0D42\u0D32\u0D48",
"\u0D06\u0D17\u0D38\u0D4D\u0D31\u0D4D\u0D31\u0D4D",
"\u0D38\u0D46\u0D2A\u0D4D\u200C\u0D31\u0D4D\u0D31\u0D02\u0D2C\u0D30\u0D4D\u200D",
"\u0D12\u0D15\u0D4D\u200C\u0D1F\u0D4B\u0D2C\u0D30\u0D4D\u200D",
"\u0D28\u0D35\u0D02\u0D2C\u0D30\u0D4D\u200D",
"\u0D21\u0D3F\u0D38\u0D02\u0D2C\u0D30\u0D4D\u200D",
}
// monthNamesMalayalamAbbr list the month name abbreviations in the Malayalam.
monthNamesMalayalamAbbr = []string{
"\u0D1C\u0D28\u0D41",
"\u0D2B\u0D46\u0D2C\u0D4D\u0D30\u0D41",
"\u0D2E\u0D3E\u0D7C",
"\u0D0F\u0D2A\u0D4D\u0D30\u0D3F",
"\u0D2E\u0D47\u0D2F\u0D4D",
"\u0D1C\u0D42\u0D7A",
"\u0D1C\u0D42\u0D32\u0D48",
"\u0D13\u0D17",
"\u0D38\u0D46\u0D2A\u0D4D\u0D31\u0D4D\u0D31\u0D02",
"\u0D12\u0D15\u0D4D\u0D1F\u0D4B",
"\u0D28\u0D35\u0D02",
"\u0D21\u0D3F\u0D38\u0D02",
}
// monthNamesMaltese list the month names in the Maltese.
monthNamesMaltese = []string{"Jannar", "Frar", "Marzu", "April", "Mejju", "Ġunju", "Lulju", "Awwissu", "Settembru", "Ottubru", "Novembru", "Diċembru"}
// monthNamesMalteseAbbr list the month name abbreviations in the Maltese.
monthNamesMalteseAbbr = []string{"Jan", "Fra", "Mar", "Apr", "Mej", "Ġun", "Lul", "Aww", "Set", "Ott", "Nov", "Diċ"}
// monthNamesMaori list the month names in the Maori.
monthNamesMaori = []string{"Kohitātea", "Huitanguru", "Poutūterangi", "Paengawhāwhā", "Haratua", "Pipiri", "Hōngongoi", "Hereturikōkā", "Mahuru", "Whiringa ā-nuku", "Whiringa ā-rangi", "Hakihea"}
// monthNamesMaoriAbbr list the month name abbreviations in the Maori.
monthNamesMaoriAbbr = []string{"Kohi", "Hui", "Pou", "Pae", "Hara", "Pipi", "Hōngo", "Here", "Mahu", "Nuku", "Rangi", "Haki"}
// monthNamesMapudungun list the month name abbreviations in the Mapudungun.
monthNamesMapudungun = []string{"Kiñe Tripantu", "Epu", "Kila", "Meli", "Kechu", "Cayu", "Regle", "Purha", "Aiya", "Marhi", "Marhi Kiñe", "Marhi Epu"}
// monthNamesMarathi list the month names in the Marathi.
monthNamesMarathi = []string{
"\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940",
"\u092B\u0947\u092C\u094D\u0930\u0941\u0935\u093E\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u090F\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u0948",
"\u0911\u0917\u0938\u094D\u091F",
"\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930",
"\u0911\u0915\u094D\u091F\u094B\u092C\u0930",
"\u0928\u094B\u0935\u094D\u0939\u0947\u0902\u092C\u0930",
"\u0921\u093F\u0938\u0947\u0902\u092C\u0930",
}
// monthNamesMarathiAbbr lists the month name abbreviations in Marathi.
monthNamesMarathiAbbr = []string{
"\u091C\u093E\u0928\u0947.",
"\u092B\u0947\u092C\u094D\u0930\u0941.",
"\u092E\u093E\u0930\u094D\u091A",
"\u090F\u092A\u094D\u0930\u093F",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u0948",
"\u0911\u0917.",
"\u0938\u092A\u094D\u091F\u0947\u0902.",
"\u0911\u0915\u094D\u091F\u094B.",
"\u0928\u094B\u0935\u094D\u0939\u0947\u0902.",
"\u0921\u093F\u0938\u0947\u0902.",
}
// monthNamesMohawk list the month names in the Mohawk.
monthNamesMohawk = []string{"Tsothohrkó:Wa", "Enniska", "Enniskó:Wa", "Onerahtókha", "Onerahtohkó:Wa", "Ohiari:Ha", "Ohiarihkó:Wa", "Seskéha", "Seskehkó:Wa", "Kenténha", "Kentenhkó:Wa", "Tsothóhrha"}
// monthNamesMongolian list the month names in the Mongolian.
monthNamesMongolian = []string{
"\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440",
"\u0425\u043E\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0414\u04E9\u0440\u04E9\u0432\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440",
"\u0422\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0417\u0443\u0440\u0433\u0430\u0430\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0414\u043E\u043B\u043E\u043E\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u041D\u0430\u0439\u043C\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0415\u0441\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440",
"\u0410\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
"\u0410\u0440\u0432\u0430\u043D \u043D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440",
"\u0410\u0440\u0432\u0430\u043D \u0445\u043E\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440",
}
// monthNamesMongolianAbbr lists the month name abbreviations in Mongolian.
monthNamesMongolianAbbr = []string{
"1-\u0440 \u0441\u0430\u0440",
"2-\u0440 \u0441\u0430\u0440",
"3-\u0440 \u0441\u0430\u0440",
"4-\u0440 \u0441\u0430\u0440",
"5-\u0440 \u0441\u0430\u0440",
"6-\u0440 \u0441\u0430\u0440",
"7-\u0440 \u0441\u0430\u0440",
"8-\u0440 \u0441\u0430\u0440",
"9-\u0440 \u0441\u0430\u0440",
"10-\u0440 \u0441\u0430\u0440",
"11-\u0440 \u0441\u0430\u0440",
"12-\u0440 \u0441\u0430\u0440",
}
// monthNamesMoroccoAbbr lists the month name abbreviations in the Morocco.
monthNamesMoroccoAbbr = []string{"jan.", "fév.", "mar.", "avr.", "mai", "jui.", "juil.", "août", "sept.", "oct.", "nov.", "déc."}
// monthNamesNepali list the month names in the Nepali.
monthNamesNepali = []string{
"\u091C\u0928\u0935\u0930\u0940",
"\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u0905\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u093E\u0908",
"\u0905\u0917\u0938\u094D\u0924",
"\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930",
"\u0905\u0915\u094D\u091F\u094B\u092C\u0930",
"\u0928\u094B\u092D\u0947\u092E\u094D\u092C\u0930",
"\u0921\u093F\u0938\u0947\u092E\u094D\u092C\u0930",
}
// monthNamesNepaliAbbr lists the month name abbreviations in the Nepali.
monthNamesNepaliAbbr = []string{
"\u091C\u0928",
"\u092B\u0947\u092C",
"\u092E\u093E\u0930\u094D\u091A",
"\u0905\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u093E\u0908",
"\u0905\u0917",
"\u0938\u0947\u092A\u094D\u091F",
"\u0905\u0915\u094D\u091F",
"\u0928\u094B\u092D",
"\u0921\u093F\u0938",
}
// monthNamesNepaliIN list the month names in the India Nepali.
monthNamesNepaliIN = []string{
"\u091C\u0928\u0935\u0930\u0940",
"\u092B\u0930\u0935\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u0905\u092A\u094D\u0930\u0947\u0932",
"\u092E\u0908",
"\u091C\u0941\u0928",
"\u091C\u0941\u0932\u093E\u0908",
"\u0905\u0917\u0938\u094D\u091F",
"\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930",
"\u0905\u0915\u094D\u091F\u094B\u092C\u0930",
"\u0928\u094B\u092D\u0947\u092E\u094D\u092C\u0930",
"\u0926\u093F\u0938\u092E\u094D\u092C\u0930",
}
// monthNamesNepaliINAbbr lists the month name abbreviations in the India Nepali.
monthNamesNepaliINAbbr = []string{
"\u091C\u0928\u0935\u0930\u0940",
"\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u0905\u092A\u094D\u0930\u093F",
"\u092E\u0947",
"\u091C\u0941\u0928",
"\u091C\u0941\u0932\u093E",
"\u0905\u0917\u0938\u094D\u091F",
"\u0938\u0947\u092A\u094D\u091F\u0947\u092E\u094D\u092C\u0930",
"\u0905\u0915\u094D\u091F\u094B",
"\u0928\u094B\u092D\u0947",
"\u0921\u093F\u0938\u0947",
}
// monthNamesNigeria list the month names in the Nigeria.
monthNamesNigeria = []string{"samwiee", "feeburyee", "marsa", "awril", "me", "suyeŋ", "sulyee", "ut", "satambara", "oktoobar", "nowamburu", "deesamburu"}
// monthNamesNigeriaAbbr lists the month name abbreviations in the Nigeria.
monthNamesNigeriaAbbr = []string{"samw", "feeb", "mar", "awr", "me", "suy", "sul", "ut", "sat", "okt", "now", "dees"}
// monthNamesNorwegian list the month names in the Norwegian.
monthNamesNorwegian = []string{"januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember"}
// monthNamesOccitan list the month names in the Occitan.
monthNamesOccitan = []string{"genièr", "febrièr", "març", "abril", "mai", "junh", "julhet", "agost", "setembre", "octobre", "novembre", "decembre"}
// monthNamesOccitanAbbr lists the month name abbreviations in the Occitan.
monthNamesOccitanAbbr = []string{"gen.", "feb.", "març", "abr.", "mai", "junh", "julh", "ag.", "set.", "oct.", "nov.", "dec."}
// monthNamesOdia list the month names in the Odia.
monthNamesOdia = []string{
"\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40",
"\u0B2B\u0B47\u0B2C\u0B43\u0B06\u0B30\u0B40",
"\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A",
"\u0B0F\u0B2A\u0B4D\u0B30\u0B3F\u0B32\u0B4D\u200C",
"\u0B2E\u0B47",
"\u0B1C\u0B41\u0B28\u0B4D\u200C",
"\u0B1C\u0B41\u0B32\u0B3E\u0B07",
"\u0B05\u0B17\u0B37\u0B4D\u0B1F",
"\u0B38\u0B47\u0B2A\u0B4D\u0B1F\u0B47\u0B2E\u0B4D\u0B2C\u0B30",
"\u0B05\u0B15\u0B4D\u0B1F\u0B4B\u0B2C\u0B30",
"\u0B28\u0B2D\u0B47\u0B2E\u0B4D\u0B2C\u0B30",
"\u0B21\u0B3F\u0B38\u0B47\u0B2E\u0B4D\u0B2C\u0B30",
}
// monthNamesOromo list the month names in the Oromo.
monthNamesOromo = []string{"Amajjii", "Guraandhala", "Bitooteessa", "Elba", "Caamsa", "Waxabajjii", "Adooleessa", "Hagayya", "Fuulbana", "Onkololeessa", "Sadaasa", "Muddee"}
// monthNamesOromoAbbr list the month abbreviations in the Oromo.
monthNamesOromoAbbr = []string{"Ama", "Gur", "Bit", "Elb", "Cam", "Wax", "Ado", "Hag", "Ful", "Onk", "Sad", "Mud"}
// monthNamesPashto list the month names in the Pashto.
monthNamesPashto = []string{
"\u0633\u0644\u0648\u0627\u063A\u0647",
"\u0643\u0628",
"\u0648\u0631\u0649",
"\u063A\u0648\u064A\u0649",
"\u063A\u0628\u0631\u06AB\u0648\u0644\u0649",
"\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649",
"\u0632\u0645\u0631\u0649",
"\u0648\u0696\u0649",
"\u062A\u0644\u0647",
"\u0644\u0693\u0645",
"\u0644\u0646\u0688 \u06CD",
"\u0645\u0631\u063A\u0648\u0645\u0649",
}
// monthNamesPersian list the month names in the Persian.
monthNamesPersian = []string{
"\u0698\u0627\u0646\u0648\u064A\u0647",
"\u0641\u0648\u0631\u064A\u0647",
"\u0645\u0627\u0631\u0633",
"\u0622\u0648\u0631\u064A\u0644",
"\u0645\u0647",
"\u0698\u0648\u0626\u0646",
"\u0698\u0648\u0626\u064A\u0647",
"\u0627\u0648\u062A",
"\u0633\u067E\u062A\u0627\u0645\u0628\u0631",
"\u0627\u064F\u0643\u062A\u0628\u0631",
"\u0646\u0648\u0627\u0645\u0628\u0631",
"\u062F\u0633\u0627\u0645\u0628\u0631",
}
// monthNamesPolish list the month names in the Polish.
monthNamesPolish = []string{"styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"}
// monthNamesPortuguese list the month names in the Portuguese.
monthNamesPortuguese = []string{"janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"}
// monthNamesPunjabi list the month names in the Punjabi.
monthNamesPunjabi = []string{
"\u0A1C\u0A28\u0A35\u0A30\u0A40",
"\u0A2B\u0A3C\u0A30\u0A35\u0A30\u0A40",
"\u0A2E\u0A3E\u0A30\u0A1A",
"\u0A05\u0A2A\u0A4D\u0A30\u0A48\u0A32",
"\u0A2E\u0A08",
"\u0A1C\u0A42\u0A28",
"\u0A1C\u0A41\u0A32\u0A3E\u0A08",
"\u0A05\u0A17\u0A38\u0A24",
"\u0A38\u0A24\u0A70\u0A2C\u0A30",
"\u0A05\u0A15\u0A24\u0A42\u0A2C\u0A30",
"\u0A28\u0A35\u0A70\u0A2C\u0A30",
"\u0A26\u0A38\u0A70\u0A2C\u0A30",
}
// monthNamesPunjabiArab list the month names in the Punjabi Arab.
monthNamesPunjabiArab = []string{
"\u062C\u0646\u0648\u0631\u06CC",
"\u0641\u0631\u0648\u0631\u06CC",
"\u0645\u0627\u0631\u0686",
"\u0627\u067E\u0631\u06CC\u0644",
"\u0645\u0626\u06CC",
"\u062C\u0648\u0646",
"\u062C\u0648\u0644\u0627\u0626\u06CC",
"\u0627\u06AF\u0633\u062A",
"\u0633\u062A\u0645\u0628\u0631",
"\u0627\u06A9\u062A\u0648\u0628\u0631",
"\u0646\u0648\u0645\u0628\u0631",
"\u062F\u0633\u0645\u0628\u0631",
}
// monthNamesQuechua list the month names in the Quechua.
monthNamesQuechua = []string{"Qulla puquy", "Hatun puquy", "Pauqar waray", "ayriwa", "Aymuray", "Inti raymi", "Anta Sitwa", "Qhapaq Sitwa", "Uma raymi", "Kantaray", "Ayamarq'a", "Kapaq Raymi"}
// monthNamesQuechuaEcuador list the month names in the Quechua Ecuador.
monthNamesQuechuaEcuador = []string{"kulla", "panchi", "pawkar", "ayriwa", "aymuray", "raymi", "sitwa", "karwa", "kuski", "wayru", "sasi", "kapak"}
// monthNamesRomanian list the month names in the Romanian.
monthNamesRomanian = []string{"ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie"}
// monthNamesRomanianAbbr list the month abbreviations in the Romanian.
monthNamesRomanianAbbr = []string{"ian.", "feb.", "mar.", "apr.", "mai", "iun.", "iul.", "aug.", "sept.", "oct.", "nov.", "dec."}
// monthNamesRomansh list the month names in the Romansh.
monthNamesRomansh = []string{"schaner", "favrer", "mars", "avrigl", "matg", "zercladur", "fanadur", "avust", "settember", "october", "november", "december"}
// monthNamesRomanshAbbr list the month abbreviations in the Romansh.
monthNamesRomanshAbbr = []string{"schan.", "favr.", "mars", "avr.", "matg", "zercl.", "fan.", "avust", "sett.", "oct.", "nov.", "dec."}
// monthNamesRussian list the month names in the Russian.
monthNamesRussian = []string{
"\u044F\u043D\u0432\u0430\u0440\u044C",
"\u0444\u0435\u0432\u0440\u0430\u043B\u044C",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0435\u043B\u044C",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D\u044C",
"\u0438\u044E\u043B\u044C",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C",
"\u043E\u043A\u0442\u044F\u0431\u0440\u044C",
"\u043D\u043E\u044F\u0431\u0440\u044C",
"\u0434\u0435\u043A\u0430\u0431\u0440\u044C",
}
// monthNamesRussianAbbr list the month abbreviations in the Russian.
monthNamesRussianAbbr = []string{
"\u044F\u043D\u0432.",
"\u0444\u0435\u0432.",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440.",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D\u044C",
"\u0438\u044E\u043B\u044C",
"\u0430\u0432\u0433.",
"\u0441\u0435\u043D.",
"\u043E\u043A\u0442.",
"\u043D\u043E\u044F.",
"\u0434\u0435\u043A.",
}
// monthNamesSakha list the month names in the Sakha.
monthNamesSakha = []string{
"\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443",
"\u041E\u043B\u0443\u043D\u043D\u044C\u0443",
"\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440",
"\u041C\u0443\u0443\u0441 \u0443\u0441\u0442\u0430\u0440",
"\u042B\u0430\u043C \u044B\u0439\u0430",
"\u0411\u044D\u0441 \u044B\u0439\u0430",
"\u041E\u0442 \u044B\u0439\u0430",
"\u0410\u0442\u044B\u0440\u0434\u044C\u0430\u0445 \u044B\u0439\u0430",
"\u0411\u0430\u043B\u0430\u0495\u0430\u043D \u044B\u0439\u0430",
"\u0410\u043B\u0442\u044B\u043D\u043D\u044C\u044B",
"\u0421\u044D\u0442\u0438\u043D\u043D\u044C\u0438",
"\u0410\u0445\u0441\u044B\u043D\u043D\u044C\u044B",
}
// monthNamesSakhaAbbr list the month abbreviations in the Sakha.
monthNamesSakhaAbbr = []string{
"\u0422\u0445\u0441",
"\u041E\u043B\u043D",
"\u041A\u043B\u043D",
"\u041C\u0441\u0443",
"\u042B\u0430\u043C",
"\u0411\u044D\u0441",
"\u041E\u0442\u044B",
"\u0410\u0442\u0440",
"\u0411\u043B\u0495",
"\u0410\u043B\u0442",
"\u0421\u044D\u0442",
"\u0410\u0445\u0441",
}
// monthNamesSami list the month names in the Sami.
monthNamesSami = []string{"uđđâivemáánu", "kuovâmáánu", "njuhčâmáánu", "cuáŋuimáánu", "vyesimáánu", "kesimáánu", "syeinimáánu", "porgemáánu", "čohčâmáánu", "roovvâdmáánu", "skammâmáánu", "juovlâmáánu"}
// monthNamesSamiAbbr list the month abbreviations in the Sami.
monthNamesSamiAbbr = []string{"uđiv", "kuov", "njuh", "cuáŋ", "vyes", "kesi", "syei", "porg", "čohč", "roov", "skam", "juov"}
// monthNamesSamiLule list the month names in the Sami (Lule).
monthNamesSamiLule = []string{"ådåjakmánno", "guovvamánno", "sjnjuktjamánno", "vuoratjismánno", "moarmesmánno", "biehtsemánno", "sjnjilltjamánno", "bårggemánno", "ragátmánno", "gålgådismánno", "basádismánno", "javllamánno"}
// monthNamesSamiLuleAbbr list the month abbreviations in the Sami (Lule).
monthNamesSamiLuleAbbr = []string{"ådåj", "guov", "snju", "vuor", "moar", "bieh", "snji", "bårg", "ragá", "gålg", "basá", "javl"}
// monthNamesSamiNorthern list the month names in the Sami (Northern).
monthNamesSamiNorthern = []string{"ođđajagemánnu", "guovvamánnu", "njukčamánnu", "cuoŋománnu", "miessemánnu", "geassemánnu", "suoidnemánnu", "borgemánnu", "čakčamánnu", "golggotmánnu", "skábmamánnu", "juovlamánnu"}
// monthNamesSamiNorthernAbbr list the month abbreviations in the Sami (Northern).
monthNamesSamiNorthernAbbr = []string{"ođđj", "guov", "njuk", "cuoŋ", "mies", "geas", "suoi", "borg", "čakč", "golg", "skáb", "juov"}
// monthNamesSamiSkolt list the month names in the Sami (Skolt).
monthNamesSamiSkolt = []string{"ođđee´jjmään", "tä´lvvmään", "pâ´zzlâšttam-mään", "njuhččmään", "vue´ssmään", "ǩie´ssmään", "suei´nnmään", "på´rǧǧmään", "čõhččmään", "kålggmään", "skamm-mään", "rosttovmään"}
// monthNamesSamiSouthern list the month names in the Sami (Southern).
monthNamesSamiSouthern = []string{"tsïengele", "goevte", "njoktje", "voerhtje", "suehpede", "ruffie", "snjaltje", "mïetske", "skïerede", "golke", "rahka", "goeve"}
// monthNamesSamiSouthernAbbr list the month abbreviations in the Sami (Southern).
monthNamesSamiSouthernAbbr = []string{"tsïen", "goevt", "njok", "voer", "sueh", "ruff", "snja", "mïet", "skïer", "golk", "rahk", "goev"}
// monthNamesSanskrit list the month names in the Sanskrit.
monthNamesSanskrit = []string{
"\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940",
"\u092B\u0947\u092C\u094D\u0930\u0941\u0905\u0930\u0940",
"\u092E\u093E\u0930\u094D\u091A",
"\u090F\u092A\u094D\u0930\u093F\u0932",
"\u092E\u0947",
"\u091C\u0942\u0928",
"\u091C\u0941\u0932\u0948",
"\u0911\u0917\u0938\u094D\u091F",
"\u0938\u092A\u094D\u091F\u0947\u0902\u092C\u0930",
"\u0911\u0915\u094D\u091F\u094B\u092C\u0930",
"\u0928\u094B\u0935\u094D\u0939\u0947\u0902\u092C\u0930",
"\u0921\u093F\u0938\u0947\u0902\u092C\u0930",
}
// monthNamesScottishGaelic list the month names in the Scottish Gaelic.
monthNamesScottishGaelic = []string{"Am Faoilleach", "An Gearran", "Am Màrt", "An Giblean", "An Cèitean", "An t-Ògmhios", "An t-Iuchar", "An Lùnastal", "An t-Sultain", "An Dàmhair", "An t-Samhain", "An Dùbhlachd"}
// monthNamesScottishGaelicAbbr list the month abbreviations in the ScottishGaelic.
monthNamesScottishGaelicAbbr = []string{"Faoi", "Gear", "Màrt", "Gibl", "Cèit", "Ògmh", "Iuch", "Lùna", "Sult", "Dàmh", "Samh", "Dùbh"}
// monthNamesSerbian list the month names in the Serbian (Cyrillic).
monthNamesSerbian = []string{
"\u0458\u0430\u043D\u0443\u0430\u0440",
"\u0444\u0435\u0431\u0440\u0443\u0430\u0440",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0438\u043B",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D",
"\u0458\u0443\u043B",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440",
"\u043E\u043A\u0442\u043E\u0431\u0430\u0440",
"\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440",
"\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440",
}
// monthNamesSerbianAbbr lists the month name abbreviations in the Serbian
// (Cyrillic).
monthNamesSerbianAbbr = []string{
"\u0458\u0430\u043D.",
"\u0444\u0435\u0431.",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440.",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D",
"\u0458\u0443\u043B",
"\u0430\u0432\u0433.",
"\u0441\u0435\u043F\u0442.",
"\u043E\u043A\u0442.",
"\u043D\u043E\u0432.",
"\u0434\u0435\u0446.",
}
// monthNamesSerbianBA list the month names in the Serbian (Cyrillic) Bosnia
// and Herzegovina.
monthNamesSerbianBA = []string{
"\u0458\u0430\u043D\u0443\u0430\u0440",
"\u0444\u0435\u0431\u0440\u0443\u0430\u0440",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0438\u043B",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D\u0438",
"\u0458\u0443\u043B\u0438",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043F\u0442\u0435\u043C\u0431\u0430\u0440",
"\u043E\u043A\u0442\u043E\u0431\u0430\u0440",
"\u043D\u043E\u0432\u0435\u043C\u0431\u0430\u0440",
"\u0434\u0435\u0446\u0435\u043C\u0431\u0430\u0440",
}
// monthNamesSerbianBAAbbr lists the month name abbreviations in the Serbian
// (Cyrillic) Bosnia and Herzegovina.
monthNamesSerbianBAAbbr = []string{
"\u0458\u0430\u043D",
"\u0444\u0435\u0431",
"\u043C\u0430\u0440",
"\u0430\u043F\u0440",
"\u043C\u0430\u0458",
"\u0458\u0443\u043D",
"\u0458\u0443\u043B",
"\u0430\u0432\u0433",
"\u0441\u0435\u043F",
"\u043E\u043A\u0442",
"\u043D\u043E\u0432",
"\u0434\u0435\u0446",
}
// monthNamesSerbianLatin list the month names in the Serbian (Latin).
monthNamesSerbianLatin = []string{"januar", "februar", "mart", "april", "maj", "jun", "jul", "avgust", "septembar", "oktobar", "novembar", "decembar"}
// monthNamesSerbianLatinAbbr lists the month name abbreviations in the
// Serbian(Latin) and Montenegro (Former).
monthNamesSerbianLatinAbbr = []string{"jan.", "feb.", "mart", "apr.", "maj", "jun", "jul", "avg.", "sept.", "okt.", "nov.", "dec."}
// monthNamesSesothoSaLeboa list the month names in the Sesotho sa Leboa.
monthNamesSesothoSaLeboa = []string{"Janaware", "Feberware", "Matšhe", "Aprele", "Mei", "June", "Julae", "Agostose", "Setemere", "Oktoboro", "Nofemere", "Disemere"}
// monthNamesSesothoSaLeboaAbbr lists the month name abbreviations in the
// Sesotho sa Leboa.
monthNamesSesothoSaLeboaAbbr = []string{"Jan", "Feb", "Matš", "Apr", "Mei", "June", "Julae", "Agost", "Set", "Oky", "Nof", "Dis"}
// monthNamesSetswana list the month names in the Setswana.
monthNamesSetswana = []string{"Ferikgong", "Tlhakole", "Mopitlwe", "Moranang", "Motsheganang", "Seetebosigo", "Phukwi", "Phatwe", "Lwetse", "Diphalane", "Ngwanatsele", "Sedimonthole"}
// monthNamesSetswanaAbbr lists the month name abbreviations in the Setswana.
monthNamesSetswanaAbbr = []string{"Fer.", "Tlh.", "Mop.", "Mor.", "Motsh.", "Seet.", "Phk.", "Pht.", "Lwetse.", "Diph.", "Ngwn.", "Sed."}
// monthNamesSindhi list the month names in the Sindhi.
monthNamesSindhi = []string{
"\u062C\u0646\u0648\u0631\u064A",
"\u0641\u0631\u0648\u0631\u064A",
"\u0645\u0627\u0631\u0686",
"\u0627\u067E\u0631\u064A\u0644",
"\u0645\u0654\u064A",
"\u062C\u0648\u0646",
"\u062C\u0648\u0644\u0627\u0621\u0650",
"\u0622\u06AF\u0633\u062A",
"\u0633\u062A\u0645\u0628\u0631",
"\u0622\u06A9\u062A\u0648\u0628\u0631",
"\u0646\u0648\u0645\u0628\u0631",
"\u068A\u0633\u0645\u0628\u0631",
}
// monthNamesSinhala list the month names in the Sinhala.
monthNamesSinhala = []string{
"\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2",
"\u0DB4\u0DD9\u0DB6\u0DBB\u0DC0\u0DCF\u0DBB\u0DD2",
"\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4",
"\u0D85\u0DB4\u0DCA\u200D\u0DBB\u0DDA\u0DBD\u0DCA",
"\u0DB8\u0DD0\u0DBA\u0DD2",
"\u0DA2\u0DD6\u0DB1\u0DD2",
"\u0DA2\u0DD6\u0DBD\u0DD2",
"\u0D85\u0D9C\u0DDD\u0DC3\u0DCA\u0DAD\u0DD4",
"\u0DC3\u0DD0\u0DB4\u0DCA\u0DAD\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA",
"\u0D94\u0D9A\u0DCA\u0DAD\u0DDD\u0DB6\u0DBB\u0DCA",
"\u0DB1\u0DDC\u0DC0\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA",
"\u0DAF\u0DD9\u0DC3\u0DD0\u0DB8\u0DCA\u0DB6\u0DBB\u0DCA",
}
// monthNamesSinhalaAbbr lists the month name abbreviations in Sinhala.
monthNamesSinhalaAbbr = []string{
"\u0DA2\u0DB1.",
"\u0DB4\u0DD9\u0DB6.",
"\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4.",
"\u0D85\u0DB4\u0DCA\u200D\u0DBB\u0DDA\u0DBD\u0DCA.",
"\u0DB8\u0DD0\u0DBA\u0DD2",
"\u0DA2\u0DD6\u0DB1\u0DD2",
"\u0DA2\u0DD6\u0DBD\u0DD2",
"\u0D85\u0D9C\u0DDD.",
"\u0DC3\u0DD0\u0DB4\u0DCA.",
"\u0D94\u0D9A\u0DCA.",
"\u0DB1\u0DDC\u0DC0\u0DD0.",
"\u0DAF\u0DD9\u0DC3\u0DD0.",
}
// monthNamesSlovak list the month names in the Slovak.
monthNamesSlovak = []string{"január", "február", "marec", "apríl", "máj", "jún", "júl", "august", "september", "október", "november", "december"}
// monthNamesSlovenian list the month names in the Slovenian.
monthNamesSlovenian = []string{"januar", "februar", "marec", "april", "maj", "junij", "julij", "avgust", "september", "oktober", "november", "december"}
// monthNamesSlovenianAbbr list the month abbreviations in the Slovenian.
monthNamesSlovenianAbbr = []string{"jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "avg.", "sep.", "okt.", "nov.", "dec."}
// monthNamesSomali list the month names in the Somali.
monthNamesSomali = []string{"Jannaayo", "Febraayo", "Maarso", "Abriil", "May", "Juun", "Luuliyo", "Ogost", "Sebtembar", "Oktoobar", "Nofembar", "Desembar"}
// monthNamesSomaliAbbr list the month abbreviations in the Somali.
monthNamesSomaliAbbr = []string{"Jan", "Feb", "Mar", "Abr", "May", "Jun", "Lul", "Ogs", "Seb", "Okt", "Nof", "Dis"}
// monthNamesSotho list the month names in the Sotho.
monthNamesSotho = []string{"Phesekgong", "Hlakola", "Hlakubele", "Mmese", "Motsheanong", "Phupjane", "Phupu", "Phata", "Leotshe", "Mphalane", "Pundungwane", "Tshitwe"}
// monthNamesSothoAbbr list the month abbreviations in the Sotho.
monthNamesSothoAbbr = []string{"Phe", "Kol", "Ube", "Mme", "Mot", "Jan", "Upu", "Pha", "Leo", "Mph", "Pun", "Tsh"}
// monthNamesSpanish list the month names in the Spanish.
monthNamesSpanish = []string{"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"}
// monthNamesSpanishAbbr list the month abbreviations in the Spanish.
monthNamesSpanishAbbr = []string{"ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"}
// monthNamesSpanishPE list the month names in the Spanish Peru.
monthNamesSpanishPE = []string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Setiembre", "Octubre", "Noviembre", "Diciembre"}
// monthNamesSpanishPEAbbr list the month abbreviations in the Spanish Peru.
monthNamesSpanishPEAbbr = []string{"Ene.", "Feb.", "Mar.", "Abr.", "May.", "Jun.", "Jul.", "Ago.", "Set.", "Oct.", "Nov.", "Dic."}
// monthNamesSwedish list the month names in the Swedish.
monthNamesSwedish = []string{"januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december"}
// monthNamesSwedishAbbr list the month abbreviations in the Swedish.
monthNamesSwedishAbbr = []string{"jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}
// monthNamesSwedishFIAbbr list the month abbreviations in the Swedish Finland.
monthNamesSwedishFIAbbr = []string{"jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec."}
// monthNamesSyriac list the month names in the Syriac.
monthNamesSyriac = []string{
"\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D",
"\u072B\u0712\u071B",
"\u0710\u0715\u072A",
"\u0722\u071D\u0723\u0722",
"\u0710\u071D\u072A",
"\u071A\u0719\u071D\u072A\u0722",
"\u072C\u0721\u0718\u0719",
"\u0710\u0712",
"\u0710\u071D\u0720\u0718\u0720",
"\u072C\u072B\u072A\u071D \u0729\u0715\u071D\u0721",
"\u072C\u072B\u072A\u071D \u0710\u071A\u072A\u071D",
"\u071F\u0722\u0718\u0722 \u0729\u0715\u071D\u0721",
}
// monthNamesSyriacAbbr lists the month name abbreviations in the Syriac.
monthNamesSyriacAbbr = []string{
"\u071F\u0722 \u070F\u0712",
"\u072B\u0712\u071B",
"\u0710\u0715\u072A",
"\u0722\u071D\u0723\u0722",
"\u0710\u071D\u072A",
"\u071A\u0719\u071D\u072A\u0722",
"\u072C\u0721\u0718\u0719",
"\u0710\u0712",
"\u0710\u071D\u0720\u0718\u0720",
"\u070F\u072C\u072B \u070F\u0710",
"\u070F\u072C\u072B \u070F\u0712",
"\u070F\u071F\u0722 \u070F\u0710",
}
// monthNamesSyllabics list the month names in the Syllabics.
monthNamesSyllabics = []string{
"\u152E\u14D0\u14C4\u140A\u1546",
"\u1556\u155D\u1557\u140A\u1546",
"\u14AB\u1466\u14EF",
"\u1404\u1433\u1546",
"\u14AA\u1403",
"\u152B\u14C2",
"\u152A\u14DA\u1403",
"\u140B\u14A1\u148C\u14EF",
"\u14EF\u144E\u1431\u1546",
"\u1405\u1450\u1431\u1546",
"\u14C4\u1555\u1431\u1546",
"\u144E\u14EF\u1431\u1546",
}
// monthNamesSyllabicsAbbr lists the month name abbreviations in the Syllabics.
monthNamesSyllabicsAbbr = []string{
"\u152E\u14D0\u14C4",
"\u1556\u155D\u1557",
"\u14AB\u1466\u14EF",
"\u1404\u1433\u1546",
"\u14AA\u1403",
"\u152B\u14C2",
"\u152A\u14DA\u1403",
"\u140B\u14A1\u148C",
"\u14EF\u144E\u1431",
"\u1405\u1450\u1431",
"\u14C4\u1555\u1431",
"\u144E\u14EF\u1431",
}
// monthNamesTajik list the month names in the Tajik.
monthNamesTajik = []string{
"\u044F\u043D\u0432\u0430\u0440",
"\u0444\u0435\u0432\u0440\u0430\u043B",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0435\u043B",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D",
"\u0438\u044E\u043B",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043D\u0442\u044F\u0431\u0440",
"\u043E\u043A\u0442\u044F\u0431\u0440",
"\u043D\u043E\u044F\u0431\u0440",
"\u0434\u0435\u043A\u0430\u0431\u0440",
}
// monthNamesTajikAbbr lists the month name abbreviations in Tajik.
monthNamesTajikAbbr = []string{
"\u044F\u043D\u0432",
"\u0444\u0435\u0432",
"\u043C\u0430\u0440",
"\u0430\u043F\u0440",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D",
"\u0438\u044E\u043B",
"\u0430\u0432\u0433",
"\u0441\u0435\u043D",
"\u043E\u043A\u0442",
"\u043D\u043E\u044F",
"\u0434\u0435\u043A",
}
// monthNamesTamazight list the month names in the Tamazight.
monthNamesTamazight = []string{"Yennayer", "Furar", "Meghres", "Yebrir", "Magu", "Yunyu", "Yulyu", "Ghuct", "Cutenber", "Tuber", "Nunember", "Dujanbir"}
// monthNamesTamazightAbbr list the month abbreviations in the Tamazight.
monthNamesTamazightAbbr = []string{"Yen", "Fur", "Megh", "Yeb", "May", "Yun", "Yul", "Ghu", "Cut", "Tub", "Nun", "Duj"}
// monthNamesTamil list the month names in the Tamil.
monthNamesTamil = []string{
"\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF",
"\u0BAA\u0BBF\u0BAA\u0BCD\u0BB0\u0BB5\u0BB0\u0BBF",
"\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD",
"\u0B8F\u0BAA\u0BCD\u0BB0\u0BB2\u0BCD",
"\u0BAE\u0BC7",
"\u0B9C\u0BC2\u0BA9\u0BCD",
"\u0B9C\u0BC2\u0BB2\u0BC8",
"\u0B86\u0B95\u0BB8\u0BCD\u0B9F\u0BCD",
"\u0B9A\u0BC6\u0BAA\u0BCD\u0B9F\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD",
"\u0B85\u0B95\u0BCD\u0B9F\u0BCB\u0BAA\u0BB0\u0BCD",
"\u0BA8\u0BB5\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD",
"\u0B9F\u0BBF\u0B9A\u0BAE\u0BCD\u0BAA\u0BB0\u0BCD",
}
// monthNamesTamilAbbr lists the month name abbreviations in Tamil.
monthNamesTamilAbbr = []string{
"\u0B9C\u0BA9.",
"\u0BAA\u0BBF\u0BAA\u0BCD.",
"\u0BAE\u0BBE\u0BB0\u0BCD.",
"\u0B8F\u0BAA\u0BCD.",
"\u0BAE\u0BC7",
"\u0B9C\u0BC2\u0BA9\u0BCD",
"\u0B9C\u0BC2\u0BB2\u0BC8",
"\u0B86\u0B95.",
"\u0B9A\u0BC6\u0BAA\u0BCD.",
"\u0B85\u0B95\u0BCD.",
"\u0BA8\u0BB5.",
"\u0B9F\u0BBF\u0B9A.",
}
// monthNamesTatar list the month names in the Tatar.
monthNamesTatar = []string{
"\u0433\u044B\u0439\u043D\u0432\u0430\u0440",
"\u0444\u0435\u0432\u0440\u0430\u043B\u044C",
"\u043C\u0430\u0440\u0442",
"\u0430\u043F\u0440\u0435\u043B\u044C",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D\u044C",
"\u0438\u044E\u043B\u044C",
"\u0430\u0432\u0433\u0443\u0441\u0442",
"\u0441\u0435\u043D\u0442\u044F\u0431\u0440\u044C",
"\u043E\u043A\u0442\u044F\u0431\u0440\u044C",
"\u043D\u043E\u044F\u0431\u0440\u044C",
"\u0434\u0435\u043A\u0430\u0431\u0440\u044C",
}
// monthNamesTatarAbbr lists the month name abbreviations in the Tatar.
monthNamesTatarAbbr = []string{
"\u0433\u044B\u0439\u043D.",
"\u0444\u0435\u0432.",
"\u043C\u0430\u0440.",
"\u0430\u043F\u0440.",
"\u043C\u0430\u0439",
"\u0438\u044E\u043D\u044C",
"\u0438\u044E\u043B\u044C",
"\u0430\u0432\u0433.",
"\u0441\u0435\u043D.",
"\u043E\u043A\u0442.",
"\u043D\u043E\u044F\u0431.",
"\u0434\u0435\u043A.",
}
// monthNamesTelugu list the month names in the Telugu.
monthNamesTelugu = []string{
"\u0C1C\u0C28\u0C35\u0C30\u0C3F",
"\u0C2B\u0C3F\u0C2C\u0C4D\u0C30\u0C35\u0C30\u0C3F",
"\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F",
"\u0C0F\u0C2A\u0C4D\u0C30\u0C3F\u0C32\u0C4D",
"\u0C2E\u0C47",
"\u0C1C\u0C42\u0C28\u0C4D",
"\u0C1C\u0C41\u0C32\u0C48",
"\u0C06\u0C17\u0C38\u0C4D\u0C1F\u0C41",
"\u0C38\u0C46\u0C2A\u0C4D\u0C1F\u0C46\u0C02\u0C2C\u0C30\u0C4D",
"\u0C05\u0C15\u0C4D\u0C1F\u0C4B\u0C2C\u0C30\u0C4D",
"\u0C28\u0C35\u0C02\u0C2C\u0C30\u0C4D",
"\u0C21\u0C3F\u0C38\u0C46\u0C02\u0C2C\u0C30\u0C4D",
}
// monthNamesTeluguAbbr lists the month name abbreviations in the Telugu.
monthNamesTeluguAbbr = []string{
"\u0C1C\u0C28",
"\u0C2B\u0C3F\u0C2C\u0C4D\u0C30",
"\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F",
"\u0C0F\u0C2A\u0C4D\u0C30\u0C3F",
"\u0C2E\u0C47",
"\u0C1C\u0C42\u0C28\u0C4D",
"\u0C1C\u0C41\u0C32\u0C48",
"\u0C06\u0C17",
"\u0C38\u0C46\u0C2A\u0C4D\u0C1F\u0C46\u0C02",
"\u0C05\u0C15\u0C4D\u0C1F\u0C4B",
"\u0C28\u0C35\u0C02",
"\u0C21\u0C3F\u0C38\u0C46\u0C02",
}
// monthNamesThai list the month names in the Thai.
monthNamesThai = []string{
"\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21",
"\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C",
"\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21",
"\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19",
"\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21",
"\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19",
"\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21",
"\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21",
"\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19",
"\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21",
"\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19",
"\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21",
}
// monthNamesTibetan list the month names in the Tibetan.
monthNamesTibetan = []string{
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B",
"\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B",
}
// monthNamesTibetanAbbr lists the month name abbreviations in the Tibetan.
monthNamesTibetanAbbr = []string{
"\u0F5F\u0FB3\u0F0B \u0F21",
"\u0F5F\u0FB3\u0F0B \u0F22",
"\u0F5F\u0FB3\u0F0B \u0F23",
"\u0F5F\u0FB3\u0F0B \u0F24",
"\u0F5F\u0FB3\u0F0B \u0F25",
"\u0F5F\u0FB3\u0F0B \u0F26",
"\u0F5F\u0FB3\u0F0B \u0F27",
"\u0F5F\u0FB3\u0F0B \u0F28",
"\u0F5F\u0FB3\u0F0B \u0F29",
"\u0F5F\u0FB3\u0F0B \u0F21\u0F20",
"\u0F5F\u0FB3\u0F0B \u0F21\u0F21",
"\u0F5F\u0FB3\u0F0B \u0F21\u0F22",
}
// monthNamesTigrinya list the month names in the Tigrinya.
monthNamesTigrinya = []string{
"\u1325\u122A",
"\u1208\u12AB\u1272\u1275",
"\u1218\u130B\u1262\u1275",
"\u121A\u12EB\u12DD\u12EB",
"\u130D\u1295\u1266\u1275",
"\u1230\u1290",
"\u1213\u121D\u1208",
"\u1290\u1213\u1230",
"\u1218\u1235\u12A8\u1228\u121D",
"\u1325\u1245\u121D\u1272",
"\u1215\u12F3\u122D",
"\u1273\u1215\u1233\u1235",
}
// monthNamesTigrinyaAbbr lists the month name abbreviations in the
// Tigrinya.
monthNamesTigrinyaAbbr = []string{
"\u1325\u122A",
"\u1208\u12AB",
"\u1218\u130B",
"\u121A\u12EB",
"\u130D\u1295",
"\u1230\u1290",
"\u1213\u121D",
"\u1290\u1213",
"\u1218\u1235",
"\u1325\u1245",
"\u1215\u12F3",
"\u1273\u1215",
}
// monthNamesTsonga list the month names in the Tsonga.
monthNamesTsonga = []string{"Sunguti", "Nyenyenyani", "Nyenyankulu", "Dzivamisoko", "Mudyaxihi", "Khotavuxika", "Mawuwani", "Mhawuri", "Ndzhati", "Nhlangula", "Hukuri", "N\u2019wendzamhala"}
// monthNamesTsongaAbbr lists the month name abbreviations in Tsonga, this
// prevents string concatenation.
monthNamesTsongaAbbr = []string{"Sun", "Yan", "Kul", "Dzi", "Mud", "Kho", "Maw", "Mha", "Ndz", "Nhl", "Huk", "N\u2019w"}
// monthNamesTradMongolian lists the month number for use with traditional
// Mongolian.
monthNamesTradMongolian = []string{"M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12"}
// monthNamesTurkish list the month names in the Turkish.
monthNamesTurkish = []string{"Ocak", "Şubat", "Mart", "Nisan", "May\u0131s", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kas\u0131m", "Aral\u0131k"}
// monthNamesTurkishAbbr lists the month name abbreviations in Turkish, this
// prevents string concatenation.
monthNamesTurkishAbbr = []string{"Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"}
// monthNamesTurkmen list the month names in the Turkmen.
monthNamesTurkmen = []string{"Ýanwar", "Fewral", "Mart", "Aprel", "Maý", "lýun", "lýul", "Awgust", "Sentýabr", "Oktýabr", "Noýabr", "Dekabr"}
// monthNamesTurkmenAbbr lists the month name abbreviations in Turkmen, this
// prevents string concatenation.
monthNamesTurkmenAbbr = []string{"Ýan", "Few", "Mart", "Apr", "Maý", "lýun", "lýul", "Awg", "Sen", "Okt", "Noý", "Dek"}
// monthNamesUkrainian list the month names in the Ukrainian.
monthNamesUkrainian = []string{
"\u0441\u0456\u0447\u0435\u043D\u044C",
"\u043B\u044E\u0442\u0438\u0439",
"\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C",
"\u043A\u0432\u0456\u0442\u0435\u043D\u044C",
"\u0442\u0440\u0430\u0432\u0435\u043D\u044C",
"\u0447\u0435\u0440\u0432\u0435\u043D\u044C",
"\u043B\u0438\u043F\u0435\u043D\u044C",
"\u0441\u0435\u0440\u043F\u0435\u043D\u044C",
"\u0432\u0435\u0440\u0435\u0441\u0435\u043D\u044C",
"\u0436\u043E\u0432\u0442\u0435\u043D\u044C",
"\u043B\u0438\u0441\u0442\u043E\u043F\u0430\u0434",
"\u0433\u0440\u0443\u0434\u0435\u043D\u044C",
}
// monthNamesUkrainianAbbr lists the month name abbreviations in Ukrainian.
monthNamesUkrainianAbbr = []string{
"\u0421\u0456\u0447",
"\u041B\u044E\u0442",
"\u0411\u0435\u0440",
"\u041A\u0432\u0456",
"\u0422\u0440\u0430",
"\u0427\u0435\u0440",
"\u041B\u0438\u043F",
"\u0421\u0435\u0440",
"\u0412\u0435\u0440",
"\u0416\u043E\u0432",
"\u041B\u0438\u0441",
"\u0413\u0440\u0443",
}
// monthNamesUpperSorbian list the month names in the Upper Sorbian.
monthNamesUpperSorbian = []string{"januar", "februar", "měrc", "apryl", "meja", "junij", "julij", "awgust", "september", "oktober", "nowember", "december"}
// monthNamesUpperSorbianAbbr lists the month name abbreviations in the
// Upper Sorbian, this prevents string concatenation.
monthNamesUpperSorbianAbbr = []string{"jan", "feb", "měr", "apr", "mej", "jun", "jul", "awg", "sep", "okt", "now", "dec"}
// monthNamesUyghur list the month names in the Uyghur.
monthNamesUyghur = []string{
"\u064A\u0627\u0646\u06CB\u0627\u0631",
"\u0641\u06D0\u06CB\u0631\u0627\u0644",
"\u0645\u0627\u0631\u062A",
"\u0626\u0627\u067E\u0631\u06D0\u0644",
"\u0645\u0627\u064A",
"\u0626\u0649\u064A\u06C7\u0646",
"\u0626\u0649\u064A\u06C7\u0644",
"\u0626\u0627\u06CB\u063A\u06C7\u0633\u062A",
"\u0633\u06D0\u0646\u062A\u06D5\u0628\u0649\u0631",
"\u0626\u06C6\u0643\u062A\u06D5\u0628\u0649\u0631",
"\u0646\u0648\u064A\u0627\u0628\u0649\u0631",
"\u062F\u06D0\u0643\u0627\u0628\u0649\u0631",
}
// monthNamesUzbek list the month names in the Uzbek.
monthNamesUzbek = []string{"Yanvar", "Fevral", "Mart", "Aprel", "May", "Iyun", "Iyul", "Avgust", "Sentabr", "Oktabr", "Noyabr", "Dekabr"}
// monthNamesUzbekAbbr lists the month name abbreviations in the Uzbek, this
// prevents string concatenation.
monthNamesUzbekAbbr = []string{"Yan", "Fev", "Mar", "Apr", "May", "Iyn", "Iyl", "Avg", "Sen", "Okt", "Noy", "Dek"}
// monthNamesValencian list the month names in the Valencian.
monthNamesValencian = []string{"gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"}
// monthNamesValencianAbbr lists the month name abbreviations in the
// Valencian, this prevents string concatenation.
monthNamesValencianAbbr = []string{"gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des."}
// monthNamesVenda list the month names in the Venda.
monthNamesVenda = []string{"Phando", "Luhuhi", "Ṱhafamuhwe", "Lambamai", "Shundunthule", "Fulwi", "Fulwana", "Ṱhangule", "Khubvumedzi", "Tshimedzi", "Ḽara", "Nyendavhusiku"}
// monthNamesVendaAbbr lists the month name abbreviations in the Venda, this
// prevents string concatenation.
monthNamesVendaAbbr = []string{"Pha", "Luh", "Ṱhf", "Lam", "Shu", "Lwi", "Lwa", "Ṱha", "Khu", "Tsh", "Ḽar", "Nye"}
// monthNamesVietnamese list the month name used for Vietnamese
monthNamesVietnamese = []string{"Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12"}
// monthNamesVietnameseAbbr3 list the mid-form abbreviation for Vietnamese
// months.
monthNamesVietnameseAbbr3 = []string{"Thg 1", "Thg 2", "Thg 3", "Thg 4", "Thg 5", "Thg 6", "Thg 7", "Thg 8", "Thg 9", "Thg 10", "Thg 11", "Thg 12"}
// monthNamesVietnameseAbbr5 list the short-form abbreviation for Vietnamese
// months.
monthNamesVietnameseAbbr5 = []string{"T 1", "T 2", "T 3", "T 4", "T 5", "T 6", "T 7", "T 8", "T 9", "T 10", "T 11", "T 12"}
// monthNamesWelsh list the month names in the Welsh.
monthNamesWelsh = []string{"Ionawr", "Chwefror", "Mawrth", "Ebrill", "Mai", "Mehefin", "Gorffennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr"}
// monthNamesWelshAbbr lists the month name abbreviations in the Welsh, this
// prevents string concatenation.
monthNamesWelshAbbr = []string{"Ion", "Chwef", "Maw", "Ebr", "Mai", "Meh", "Gorff", "Awst", "Medi", "Hyd", "Tach", "Rhag"}
// monthNamesWolof list the month names in the Wolof.
monthNamesWolof = []string{"Samwiye", "Fewriye", "Maars", "Awril", "Me", "Suwe", "Sullet", "Ut", "Septàmbar", "Oktoobar", "Noowàmbar", "Desàmbar"}
// monthNamesWolofAbbr list the month name abbreviations in the Wolof, this
// prevents string concatenation.
monthNamesWolofAbbr = []string{"Sam.", "Few.", "Maa", "Awr.", "Me", "Suw", "Sul.", "Ut", "Sept.", "Okt.", "Now.", "Des."}
// monthNamesXhosa list the month names in the Xhosa.
monthNamesXhosa = []string{"uJanuwari", "uFebuwari", "uMatshi", "uAprili", "uMeyi", "uJuni", "uJulayi", "uAgasti", "uSeptemba", "uOktobha", "uNovemba", "uDisemba"}
// monthNamesXhosaAbbr list the month abbreviations in the Xhosa, this
// prevents string concatenation.
monthNamesXhosaAbbr = []string{"uJan.", "uFeb.", "uMat.", "uEpr.", "uMey.", "uJun.", "uJul.", "uAg.", "uSep.", "uOkt.", "uNov.", "uDis."}
// monthNamesYi list the month names in the Yi.
monthNamesYi = []string{"\uA2CD", "\uA44D", "\uA315", "\uA1D6", "\uA26C", "\uA0D8", "\uA3C3", "\uA246", "\uA22C", "\uA2B0", "\uA2B0\uA2AA", "\uA2B0\uA44B"}
// monthNamesYiSuffix lists the month names in Yi with the "\uA1AA" suffix.
monthNamesYiSuffix = []string{"\uA2CD\uA1AA", "\uA44D\uA1AA", "\uA315\uA1AA", "\uA1D6\uA1AA", "\uA26C\uA1AA", "\uA0D8\uA1AA", "\uA3C3\uA1AA", "\uA246\uA1AA", "\uA22C\uA1AA", "\uA2B0\uA1AA", "\uA2B0\uA2AA\uA1AA", "\uA2B0\uA44B\uA1AA"}
// monthNamesYiddish list the month names in the Yiddish.
monthNamesYiddish = []string{
"\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8",
"\u05E4\u05BF\u05E2\u05D1\u05E8\u05D5\u05D0\u05B7\u05E8",
"\u05DE\u05E2\u05E8\u05E5",
"\u05D0\u05B7\u05E4\u05BC\u05E8\u05D9\u05DC",
"\u05DE\u05D9\u05D9",
"\u05D9\u05D5\u05E0\u05D9",
"\u05D9\u05D5\u05DC\u05D9",
"\u05D0\u05D5\u05D9\u05D2\u05D5\u05E1\u05D8",
"\u05E1\u05E2\u05E4\u05BC\u05D8\u05E2\u05DE\u05D1\u05E2\u05E8",
"\u05D0\u05E7\u05D8\u05D0\u05D1\u05E2\u05E8",
"\u05E0\u05D0\u05D5\u05D5\u05E2\u05DE\u05D1\u05E2\u05E8",
"\u05D3\u05E2\u05E6\u05E2\u05DE\u05D1\u05E2\u05E8",
}
// monthNamesYiddishAbbr lists the month name abbreviations in Yiddish.
monthNamesYiddishAbbr = []string{
"\u05D9\u05D0\u05B7\u05E0",
"\u05E4\u05BF\u05E2\u05D1",
"\u05DE\u05E2\u05E8\u05E5",
"\u05D0\u05B7\u05E4\u05BC\u05E8",
"\u05DE\u05D9\u05D9",
"\u05D9\u05D5\u05E0\u05D9",
"\u05D9\u05D5\u05DC\u05D9",
"\u05D0\u05D5\u05D9\u05D2",
"\u05E1\u05E2\u05E4\u05BC",
"\u05D0\u05E7\u05D8",
"\u05E0\u05D0\u05D5\u05D5",
"\u05D3\u05E2\u05E6",
}
// monthNamesYoruba list the month names in the Yoruba.
monthNamesYoruba = []string{
"\u1E62\u1EB9\u0301r\u1EB9\u0301",
"Èrèlè",
"\u1EB8r\u1EB9\u0300nà",
"Ìgbé",
"\u1EB8\u0300bibi",
"Òkúdu",
"Age\u0323mo\u0323",
"Ògún",
"Owewe",
"\u1ECC\u0300wàrà",
"Bélú",
"\u1ECC\u0300p\u1EB9\u0300",
}
// monthNamesYorubaAbbr lists the month name abbreviations in the Yoruba.
monthNamesYorubaAbbr = []string{
"\u1E62\u1EB9\u0301",
"Èr",
"\u1EB8r",
"Ìg",
"\u1EB8\u0300b",
"Òk",
"Ag",
"Òg",
"Ow",
"\u1ECC\u0300w",
"Bé",
"\u1ECC\u0300p",
}
// monthNamesZulu list the month names in the Zulu.
monthNamesZulu = []string{"Januwari", "Febhuwari", "Mashi", "Ephreli", "Meyi", "Juni", "Julayi", "Agasti", "Septemba", "Okthoba", "Novemba", "Disemba"}
// monthNamesZuluAbbr list the month name abbreviations in the Zulu.
monthNamesZuluAbbr = []string{"Jan", "Feb", "Mas", "Eph", "Mey", "Jun", "Jul", "Agas", "Sep", "Okt", "Nov", "Dis"}
// weekdayNamesAfrikaans list the weekday name in the Afrikaans.
weekdayNamesAfrikaans = []string{"Sondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrydag", "Saterdag"}
// weekdayNamesAfrikaansAbbr list the weekday name abbreviations in the
// Afrikaans.
weekdayNamesAfrikaansAbbr = []string{"So.", "Ma.", "Di.", "Wo.", "Do.", "Vr.", "Sa."}
// weekdayNamesAlbanian list the weekday name in the Albanian.
weekdayNamesAlbanian = []string{"e diel", "e hënë", "e martë", "e mërkurë", "e enjte", "e premte", "e shtunë"}
// weekdayNamesAlbanianAbbr list the weekday name abbreviations in the
// Albanian.
weekdayNamesAlbanianAbbr = []string{"die", "hën", "mar", "mër", "enj", "pre", "sht"}
// weekdayNamesAlsatian list the weekday name in the Alsatian.
weekdayNamesAlsatian = []string{"Sunntig", "Määntig", "Ziischtig", "Mittwuch", "Dunschtig", "Friitig", "Samschtig"}
// weekdayNamesAlsatianAbbr list the weekday name abbreviations in the
// Alsatian.
weekdayNamesAlsatianAbbr = []string{"Su.", "Mä.", "Zi.", "Mi.", "Du.", "Fr.", "Sa."}
// weekdayNamesAlsatianFrance list the weekday name in the Alsatian France.
weekdayNamesAlsatianFrance = []string{"Sundi", "Manti", "Zischti", "Mettwuch", "Dunnerschti", "Friti", "Sàmschti"}
// weekdayNamesAlsatianFranceAbbr list the weekday name abbreviations in the
// Alsatian France.
weekdayNamesAlsatianFranceAbbr = []string{"Su.", "Ma.", "Zi.", "Me.", "Du.", "Fr.", "Sà."}
// weekdayNamesAmharic list the weekday name in the Amharic.
weekdayNamesAmharic = []string{
"\u12A5\u1211\u12F5",
"\u1230\u129E",
"\u121B\u12AD\u1230\u129E",
"\u1228\u1261\u12D5",
"\u1210\u1219\u1235",
"\u12D3\u122D\u1265",
"\u1245\u12F3\u121C",
}
// weekdayNamesAmharicAbbr list the weekday name abbreviations in the
// Amharic.
weekdayNamesAmharicAbbr = []string{
"\u12A5\u1211\u12F5",
"\u1230\u129E",
"\u121B\u12AD\u1230",
"\u1228\u1261\u12D5",
"\u1210\u1219\u1235",
"\u12D3\u122D\u1265",
"\u1245\u12F3\u121C",
}
// weekdayNamesArabic list the weekday name in the Arabic.
weekdayNamesArabic = []string{
"\u0627\u0644\u0623\u062D\u062F",
"\u0627\u0644\u0625\u062B\u0646\u064A\u0646",
"\u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621",
"\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621",
"\u0627\u0644\u062E\u0645\u064A\u0633",
"\u0627\u0644\u062C\u0645\u0639\u0629",
"\u0627\u0644\u0633\u0628\u062A",
}
// weekdayNamesArabicAbbr list the weekday name abbreviations in the Arabic.
weekdayNamesArabicAbbr = []string{
"\u0627\u0644\u0623\u062D\u062F",
"\u0627\u0644\u0625\u062B\u0646\u064A\u0646",
"\u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621",
"\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621",
"\u0627\u0644\u062E\u0645\u064A\u0633",
"\u0627\u0644\u062C\u0645\u0639\u0629",
"\u0627\u0644\u0633\u0628\u062A",
}
// weekdayNamesArmenian list the weekday name in the Armenian.
weekdayNamesArmenian = []string{
"\u053F\u056B\u0580\u0561\u056F\u056B",
"\u0535\u0580\u056F\u0578\u0582\u0577\u0561\u0562\u0569\u056B",
"\u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B",
"\u0549\u0578\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B",
"\u0540\u056B\u0576\u0563\u0577\u0561\u0562\u0569\u056B",
"\u0548\u0582\u0580\u0562\u0561\u0569",
"\u0547\u0561\u0562\u0561\u0569",
}
// weekdayNamesArmenianAbbr list the weekday name abbreviations in the
// Armenian.
weekdayNamesArmenianAbbr = []string{
"\u053F\u056B\u0580",
"\u0535\u0580\u056F",
"\u0535\u0580\u0584",
"\u0549\u0580\u0584",
"\u0540\u0576\u0563",
"\u0548\u0582\u0580",
"\u0547\u0562\u0569",
}
// weekdayNamesAssamese list the weekday name in the Assamese.
weekdayNamesAssamese = []string{
"\u09F0\u09AC\u09BF\u09AC\u09BE\u09F0",
"\u09B8\u09CB\u09AE\u09AC\u09BE\u09F0",
"\u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0",
"\u09AC\u09C1\u09A7\u09AC\u09BE\u09F0",
"\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF\u09AC\u09BE\u09F0",
"\u09B6\u09C1\u0995\u09CD\u09B0\u09AC\u09BE\u09F0",
"\u09B6\u09A8\u09BF\u09AC\u09BE\u09F0",
}
// weekdayNamesAssameseAbbr list the weekday name abbreviations in the
// Assamese.
weekdayNamesAssameseAbbr = []string{
"\u09F0\u09AC\u09BF.",
"\u09B8\u09CB\u09AE.",
"\u09AE\u0999\u09CD\u0997\u09B2.",
"\u09AC\u09C1\u09A7.",
"\u09AC\u09C3\u09B9.",
"\u09B6\u09C1\u0995\u09CD\u09B0.",
"\u09B6\u09A8\u09BF.",
}
// weekdayNamesAzerbaijaniCyrillic list the weekday name in the Azerbaijani
// (Cyrillic).
weekdayNamesAzerbaijaniCyrillic = []string{
"\u0431\u0430\u0437\u0430\u0440",
"\u0431\u0430\u0437\u0430\u0440 \u0435\u0440\u0442\u04D9\u0441\u0438",
"\u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B",
"\u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9",
"\u04B9\u04AF\u043C\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B",
"\u04B9\u04AF\u043C\u04D9",
"\u0448\u04D9\u043D\u0431\u04D9",
}
// weekdayNamesAzerbaijaniCyrillicAbbr list the weekday name abbreviations
// in the Azerbaijani (Cyrillic).
weekdayNamesAzerbaijaniCyrillicAbbr = []string{"\u0411", "\u0411\u0435", "\u0427\u0430", "\u0427", "\u04B8\u0430", "\u04B8", "\u0428"}
// weekdayNamesAzerbaijani list the weekday name in the Azerbaijani.
weekdayNamesAzerbaijani = []string{
"bazar",
"bazarçert\u0259si",
"ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131",
"ç\u0259r\u015F\u0259nb\u0259",
"cüm\u0259 ax\u015Fam\u0131",
"cüm\u0259",
"\u015F\u0259nb\u0259",
}
// weekdayNamesAzerbaijaniAbbr list the weekday name abbreviations in the
// Azerbaijani.
weekdayNamesAzerbaijaniAbbr = []string{"B.", "B.E.", "Ç.A.", "Ç.", "C.A.", "C.", "\u015E."}
// weekdayNamesBangla list the weekday name in the Bangla.
weekdayNamesBangla = []string{
"\u09B0\u09AC\u09BF\u09AC\u09BE\u09B0",
"\u09B8\u09CB\u09AE\u09AC\u09BE\u09B0",
"\u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0",
"\u09AC\u09C1\u09A7\u09AC\u09BE\u09B0",
"\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF\u09AC\u09BE\u09B0",
"\u09B6\u09C1\u0995\u09CD\u09B0\u09AC\u09BE\u09B0",
"\u09B6\u09A8\u09BF\u09AC\u09BE\u09B0",
}
// weekdayNamesBanglaAbbr list the weekday name abbreviations in the Bangla.
weekdayNamesBanglaAbbr = []string{
"\u09B0\u09AC\u09BF.",
"\u09B8\u09CB\u09AE.",
"\u09AE\u0999\u09CD\u0997\u09B2.",
"\u09AC\u09C1\u09A7.",
"\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF.",
"\u09B6\u09C1\u0995\u09CD\u09B0.",
"\u09B6\u09A8\u09BF.",
}
// weekdayNamesBashkir list the weekday name in the Bashkir.
weekdayNamesBashkir = []string{
"\u0419\u04D9\u043A\u0448\u04D9\u043C\u0431\u0435",
"\u0414\u04AF\u0448\u04D9\u043C\u0431\u0435",
"\u0428\u0438\u0448\u04D9\u043C\u0431\u0435",
"\u0428\u0430\u0440\u0448\u0430\u043C\u0431\u044B",
"\u041A\u0435\u0441\u0430\u0499\u043D\u0430",
"\u0419\u043E\u043C\u0430",
"\u0428\u04D9\u043C\u0431\u0435",
}
// weekdayNamesBashkirAbbr list the weekday name abbreviations in the
// Bashkir.
weekdayNamesBashkirAbbr = []string{
"\u0419\u0448",
"\u0414\u0448",
"\u0428\u0448",
"\u0428\u0440",
"\u041A\u0441",
"\u0419\u043C",
"\u0428\u0431",
}
// weekdayNamesBasque list the weekday name in the Basque.
weekdayNamesBasque = []string{"igandea", "astelehena", "asteartea", "asteazkena", "osteguna", "ostirala", "larunbata"}
// weekdayNamesBasqueAbbr list the weekday name abbreviations in the Basque.
weekdayNamesBasqueAbbr = []string{"ig.", "al.", "ar.", "az.", "og.", "or.", "lr."}
// weekdayNamesBelarusian list the weekday name in the Belarusian.
weekdayNamesBelarusian = []string{
"\u043D\u044F\u0434\u0437\u0435\u043B\u044F",
"\u043F\u0430\u043D\u044F\u0434\u0437\u0435\u043B\u0430\u043A",
"\u0430\u045E\u0442\u043E\u0440\u0430\u043A",
"\u0441\u0435\u0440\u0430\u0434\u0430",
"\u0447\u0430\u0446\u0432\u0435\u0440",
"\u043F\u044F\u0442\u043D\u0456\u0446\u0430",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesBelarusianAbbr list the weekday name abbreviations in the
// Belarusian.
weekdayNamesBelarusianAbbr = []string{
"\u043D\u0434",
"\u043F\u043D",
"\u0430\u045E\u0442",
"\u0441\u0440",
"\u0447\u0446",
"\u043F\u0442",
"\u0441\u0431",
}
// weekdayNamesBosnianCyrillic list the weekday name in the Bosnian
// (Cyrillic).
weekdayNamesBosnianCyrillic = []string{
"\u043D\u0435\u0434\u0458\u0435\u0459\u0430",
"\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A",
"\u0443\u0442\u043E\u0440\u0430\u043A",
"\u0441\u0440\u0438\u0458\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A",
"\u043F\u0435\u0442\u0430\u043A",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesBosnianCyrillicAbbr list the weekday name abbreviations in
// the Bosnian (Cyrillic).
weekdayNamesBosnianCyrillicAbbr = []string{
"\u043D\u0435\u0434",
"\u043F\u043E\u043D",
"\u0443\u0442\u043E",
"\u0441\u0440\u0435",
"\u0447\u0435\u0442",
"\u043F\u0435\u0442",
"\u0441\u0443\u0431",
}
// weekdayNamesBosnian list the weekday name in the Bosnian.
weekdayNamesBosnian = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"}
// weekdayNamesBosnianAbbr list the weekday name abbreviations in the
// Bosnian.
weekdayNamesBosnianAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"}
// weekdayNamesBreton list the weekday name in the Breton.
weekdayNamesBreton = []string{"Sul", "Lun", "Meurzh", "Merc'her", "Yaou", "Gwener", "Sadorn"}
// weekdayNamesBretonAbbr list the weekday name abbreviations in the Breton.
weekdayNamesBretonAbbr = []string{"Sul", "Lun", "Meu.", "Mer.", "Yaou", "Gwe.", "Sad."}
// weekdayNamesBulgarian list the weekday name in the Bulgarian.
weekdayNamesBulgarian = []string{
"\u043D\u0435\u0434\u0435\u043B\u044F",
"\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u043D\u0438\u043A",
"\u0432\u0442\u043E\u0440\u043D\u0438\u043A",
"\u0441\u0440\u044F\u0434\u0430",
"\u0447\u0435\u0442\u0432\u044A\u0440\u0442\u044A\u043A",
"\u043F\u0435\u0442\u044A\u043A",
"\u0441\u044A\u0431\u043E\u0442\u0430",
}
// weekdayNamesBulgarianAbbr list the weekday name abbreviations in the
// Bulgarian.
weekdayNamesBulgarianAbbr = []string{
"\u043D\u0435\u0434",
"\u043F\u043E\u043D",
"\u0432\u0442",
"\u0441\u0440",
"\u0447\u0435\u0442\u0432",
"\u043F\u0435\u0442",
"\u0441\u044A\u0431",
}
// weekdayNamesBurmese list the weekday name in the Burmese.
weekdayNamesBurmese = []string{
"\u1010\u1014\u1004\u103A\u1039\u1002\u1014\u103D\u1031",
"\u1010\u1014\u1004\u103A\u1039\u101C\u102C",
"\u1021\u1004\u103A\u1039\u1002\u102B",
"\u1017\u102F\u1012\u1039\u1013\u101F\u1030\u1038",
"\u1000\u103C\u102C\u101E\u1015\u1010\u1031\u1038",
"\u101E\u1031\u102C\u1000\u103C\u102C",
"\u1005\u1014\u1031",
}
// weekdayNamesCentralKurdish list the weekday name in the Central Kurdish.
weekdayNamesCentralKurdish = []string{
"\u06CC\u06D5\u06A9\u0634\u06D5\u0645\u0645\u06D5",
"\u062F\u0648\u0648\u0634\u06D5\u0645\u0645\u06D5",
"\u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5",
"\u0686\u0648\u0627\u0631\u0634\u06D5\u0645\u0645\u06D5",
"\u067E\u06CE\u0646\u062C\u0634\u06D5\u0645\u0645\u06D5",
"\u06BE\u06D5\u06CC\u0646\u06CC",
"\u0634\u06D5\u0645\u0645\u06D5",
}
// weekdayNamesCherokee list the weekday name in the Cherokee.
weekdayNamesCherokee = []string{
"\u13A4\u13BE\u13D9\u13D3\u13C6\u13CD\u13AC",
"\u13A4\u13BE\u13D9\u13D3\u13C9\u13C5\u13AF",
"\u13D4\u13B5\u13C1\u13A2\u13A6",
"\u13E6\u13A2\u13C1\u13A2\u13A6",
"\u13C5\u13A9\u13C1\u13A2\u13A6",
"\u13E7\u13BE\u13A9\u13B6\u13CD\u13D7",
"\u13A4\u13BE\u13D9\u13D3\u13C8\u13D5\u13BE",
}
// weekdayNamesCherokeeAbbr list the weekday name abbreviations in the
// Cherokee.
weekdayNamesCherokeeAbbr = []string{
"\u13C6\u13CD\u13AC",
"\u13C9\u13C5\u13AF",
"\u13D4\u13B5\u13C1",
"\u13E6\u13A2\u13C1",
"\u13C5\u13A9\u13C1",
"\u13E7\u13BE\u13A9",
"\u13C8\u13D5\u13BE",
}
// weekdayNamesCorsican list the weekday name in the Corsican.
weekdayNamesCorsican = []string{"dumenica", "luni", "marti", "mercuri", "ghjovi", "venneri", "sabbatu"}
// weekdayNamesCorsicanAbbr list the weekday name abbreviations in the Corsican.
weekdayNamesCorsicanAbbr = []string{"dum.", "lun.", "mar.", "mer.", "ghj.", "ven.", "sab."}
// weekdayNamesCroatian list the weekday name in the Croatian.
weekdayNamesCroatian = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"}
// weekdayNamesCroatianAbbr list the weekday name abbreviations in the Croatian.
weekdayNamesCroatianAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"}
// weekdayNamesCzech list the weekday name in the Czech.
weekdayNamesCzech = []string{"neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota"}
// weekdayNamesCzechAbbr list the weekday name abbreviations in the Czech.
weekdayNamesCzechAbbr = []string{"ne", "po", "út", "st", "čt", "pá", "so"}
// weekdayNamesDanish list the weekday name in the Danish.
weekdayNamesDanish = []string{"søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"}
// weekdayNamesDanishAbbr list the weekday name abbreviations in the Danish.
weekdayNamesDanishAbbr = []string{"sø", "ma", "ti", "on", "to", "fr", "lø"}
// weekdayNamesDari list the weekday name in the Dari.
weekdayNamesDari = []string{
"\u06CC\u06A9\u0634\u0646\u0628\u0647",
"\u062F\u0648\u0634\u0646\u0628\u0647",
"\u0633\u0647\u200C \u0634\u0646\u0628\u0647",
"\u0686\u0647\u0627\u0631 \u0634\u0646\u0628\u0647",
"\u067E\u0646\u062C\u0634\u0646\u0628\u0647",
"\u062C\u0645\u0639\u0647",
"\u0634\u0646\u0628\u0647",
}
// weekdayNamesDivehi list the weekday name in the Divehi.
weekdayNamesDivehi = []string{
"\u0787\u07A7\u078B\u07A9\u0787\u07B0\u078C\u07A6",
"\u0780\u07AF\u0789\u07A6",
"\u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6",
"\u0784\u07AA\u078B\u07A6",
"\u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8",
"\u0780\u07AA\u0786\u07AA\u0783\u07AA",
"\u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA",
}
// weekdayNamesDutch list the weekday name in the Dutch.
weekdayNamesDutch = []string{"zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"}
// weekdayNamesDutchAbbr list the weekday name abbreviations in the Dutch.
weekdayNamesDutchAbbr = []string{"zo", "ma", "di", "wo", "do", "vr", "za"}
// weekdayNamesDzongkha list the weekday name in the Dzongkha.
weekdayNamesDzongkha = []string{
"\u0F42\u0F5F\u0F60\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0B",
"\u0F42\u0F5F\u0F60\u0F0B\u0F49\u0F72\u0F0B\u0F58\u0F0B",
}
// weekdayNamesDzongkhaAbbr list the weekday name abbreviations in the
// Dzongkha.
weekdayNamesDzongkhaAbbr = []string{
"\u0F5F\u0FB3\u0F0B",
"\u0F58\u0F72\u0F62\u0F0B",
"\u0F63\u0FB7\u0F42\u0F0B",
"\u0F55\u0F74\u0F62\u0F0B",
"\u0F66\u0F44\u0F66\u0F0B",
"\u0F66\u0FA4\u0F7A\u0F53\u0F0B",
"\u0F49\u0F72\u0F0B",
}
// weekdayNamesChinese list the weekday name in the Chinese.
weekdayNamesChinese = []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
// weekdayNamesChineseAbbr list the weekday name abbreviations in the
// Chinese.
weekdayNamesChineseAbbr = []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
// weekdayNamesChineseAbbr list the weekday name abbreviations in the
// Chinese.
weekdayNamesChineseAbbr2 = []string{"週日", "週一", "週二", "週三", "週四", "週五", "週六"}
// weekdayNamesEnglish list the weekday name in the English.
weekdayNamesEnglish = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
// weekdayNamesEnglishAbbr list the weekday name abbreviations in the
// English.
weekdayNamesEnglishAbbr = []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
// weekdayNamesEstonian list the weekday name in the Estonian.
weekdayNamesEstonian = []string{"pühapäev", "esmaspäev", "teisipäev", "kolmapäev", "neljapäev", "reede", "laupäev"}
// weekdayNamesEstonianAbbr list the weekday name abbreviations in the
// Estonian.
weekdayNamesEstonianAbbr = []string{"P", "E", "T", "K", "N", "R", "L"}
// weekdayNamesFaroese list the weekday name in the Faroese.
weekdayNamesFaroese = []string{"sunnudagur", "mánadagur", "týsdagur", "mikudagur", "hósdagur", "fríggjadagur", "leygardagur"}
// weekdayNamesFaroeseAbbr list the weekday name abbreviations in the
// Faroese.
weekdayNamesFaroeseAbbr = []string{"sun.", "mán.", "týs.", "mik.", "hós.", "frí.", "ley."}
// weekdayNamesFilipino list the weekday name in the Filipino.
weekdayNamesFilipino = []string{"Linggo", "Lunes", "Martes", "Miyerkules", "Huwebes", "Biyernes", "Sabado"}
// weekdayNamesFilipinoAbbr list the weekday name abbreviations in the
// Filipino.
weekdayNamesFilipinoAbbr = []string{"Lin", "Lun", "Mar", "Miy", "Huw", "Biy", "Sab"}
// weekdayNamesFinnish list the weekday name in the Finnish
weekdayNamesFinnish = []string{"sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai"}
// weekdayNamesFinnishAbbr list the weekday name abbreviations in the
// Finnish.
weekdayNamesFinnishAbbr = []string{"su", "ma", "ti", "ke", "to", "pe", "la"}
// weekdayNamesFrench list the weekday name in the French.
weekdayNamesFrench = []string{"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"}
// weekdayNamesFrenchAbbr list the weekday name abbreviations in the French.
weekdayNamesFrenchAbbr = []string{"dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."}
// weekdayNamesFrisian list the weekday name in the Frisian.
weekdayNamesFrisian = []string{"snein", "moandei", "tiisdei", "woansdei", "tongersdei", "freed", "sneon"}
// weekdayNamesFrisianAbbr list the weekday name abbreviations in the
// Frisian.
weekdayNamesFrisianAbbr = []string{"sni", "moa", "tii", "woa", "ton", "fre", "sno"}
// weekdayNamesFulah list the weekday name in the Fulah.
weekdayNamesFulah = []string{"dewo", "aaɓnde", "mawbaare", "njeslaare", "naasaande", "mawnde", "hoore-biir"}
// weekdayNamesFulahAbbr list the weekday name abbreviations in the Fulah
weekdayNamesFulahAbbr = []string{"dew", "aaɓ", "maw", "nje", "naa", "mwd", "hbi"}
// weekdayNamesNigeria list the weekday name in the Nigeria
weekdayNamesNigeria = []string{"alete", "altine", "talaata", "alarba", "alkamiisa", "aljumaa", "asete"}
// weekdayNamesNigeriaAbbr list the weekday name abbreviations in the
// Nigeria.
weekdayNamesNigeriaAbbr = []string{"alet", "alt.", "tal.", "alar.", "alk.", "alj.", "aset"}
// weekdayNamesGalician list the weekday name in the Galician.
weekdayNamesGalician = []string{"domingo", "luns", "martes", "mércores", "xoves", "venres", "sábado"}
// weekdayNamesGalicianAbbr list the weekday name abbreviations in the
// Galician.
weekdayNamesGalicianAbbr = []string{"dom.", "luns", "mar.", "mér.", "xov.", "ven.", "sáb."}
// weekdayNamesGeorgian list the weekday name in the Georgian.
weekdayNamesGeorgian = []string{
"\u10D9\u10D5\u10D8\u10E0\u10D0",
"\u10DD\u10E0\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8",
"\u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8",
"\u10DD\u10D7\u10EE\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8",
"\u10EE\u10E3\u10D7\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8",
"\u10DE\u10D0\u10E0\u10D0\u10E1\u10D9\u10D4\u10D5\u10D8",
"\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8",
}
// weekdayNamesGeorgianAbbr list the weekday name abbreviations in the
// Georgian.
weekdayNamesGeorgianAbbr = []string{
"\u10D9\u10D5.",
"\u10DD\u10E0\u10E8.",
"\u10E1\u10D0\u10DB\u10E8.",
"\u10DD\u10D7\u10EE\u10E8.",
"\u10EE\u10E3\u10D7\u10E8.",
"\u10DE\u10D0\u10E0.",
"\u10E8\u10D0\u10D1.",
}
// weekdayNamesGerman list the weekday name in the German.
weekdayNamesGerman = []string{"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"}
// weekdayNamesGermanAbbr list the weekday name abbreviations in the German.
weekdayNamesGermanAbbr = []string{"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"}
// weekdayNamesGreek list the weekday name in the Greek.
weekdayNamesGreek = []string{
"\u039A\u03C5\u03C1\u03B9\u03B1\u03BA\u03AE",
"\u0394\u03B5\u03C5\u03C4\u03AD\u03C1\u03B1",
"\u03A4\u03C1\u03AF\u03C4\u03B7",
"\u03A4\u03B5\u03C4\u03AC\u03C1\u03C4\u03B7",
"\u03A0\u03AD\u03BC\u03C0\u03C4\u03B7",
"\u03A0\u03B1\u03C1\u03B1\u03C3\u03BA\u03B5\u03C5\u03AE",
"\u03A3\u03AC\u03B2\u03B2\u03B1\u03C4\u03BF",
}
// weekdayNamesGreekAbbr list the weekday name abbreviations in the Greek.
weekdayNamesGreekAbbr = []string{
"\u039A\u03C5\u03C1",
"\u0394\u03B5\u03C5",
"\u03A4\u03C1\u03B9",
"\u03A4\u03B5\u03C4",
"\u03A0\u03B5\u03BC",
"\u03A0\u03B1\u03C1",
"\u03A3\u03B1\u03B2",
}
// weekdayNamesGreenlandic list the weekday name in the Greenlandic.
weekdayNamesGreenlandic = []string{"sapaat", "ataasinngorneq", "marlunngorneq", "pingasunngorneq", "sisamanngorneq", "tallimanngorneq", "arfininngorneq"}
// weekdayNamesGreenlandicAbbr list the weekday name abbreviations in the
// Greenlandic.
weekdayNamesGreenlandicAbbr = []string{"sap.", "at.", "marl.", "ping.", "sis.", "tall.", "arf."}
// weekdayNamesGuarani list the weekday name in the Guarani.
weekdayNamesGuarani = []string{"arate\u0129", "arakõi", "araapy", "ararundy", "arapo", "arapote\u0129", "arapokõi"}
// weekdayNamesGuaraniAbbr list the weekday name abbreviations in the
// Guarani.
weekdayNamesGuaraniAbbr = []string{"te\u0129", "kõi", "apy", "ndy", "po", "ote\u0129", "okõi"}
// weekdayNamesGujarati list the weekday name in the Gujarati.
weekdayNamesGujarati = []string{
"\u0AB0\u0AB5\u0ABF\u0AB5\u0ABE\u0AB0",
"\u0AB8\u0ACB\u0AAE\u0AB5\u0ABE\u0AB0",
"\u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0",
"\u0AAC\u0AC1\u0AA7\u0AB5\u0ABE\u0AB0",
"\u0A97\u0AC1\u0AB0\u0AC1\u0AB5\u0ABE\u0AB0",
"\u0AB6\u0AC1\u0A95\u0ACD\u0AB0\u0AB5\u0ABE\u0AB0",
"\u0AB6\u0AA8\u0ABF\u0AB5\u0ABE\u0AB0",
}
// weekdayNamesGujaratiAbbr list the weekday name abbreviations in the
// Gujarati.
weekdayNamesGujaratiAbbr = []string{
"\u0AB0\u0AB5\u0ABF",
"\u0AB8\u0ACB\u0AAE",
"\u0AAE\u0A82\u0A97\u0AB3",
"\u0AAC\u0AC1\u0AA7",
"\u0A97\u0AC1\u0AB0\u0AC1",
"\u0AB6\u0AC1\u0A95\u0ACD\u0AB0",
"\u0AB6\u0AA8\u0ABF",
}
// weekdayNamesHausa list the weekday name in the Hausa.
weekdayNamesHausa = []string{"Lahadi", "Litinin", "Talata", "Laraba", "Alhamis", "Jumma\u02bca", "Asabar"}
// weekdayNamesHausaAbbr list the weekday name abbreviations in the Hausa.
weekdayNamesHausaAbbr = []string{"Lah", "Lit", "Tal", "Lar", "Alh", "Jum", "Asa"}
// weekdayNamesHawaiian list the weekday name in the Hawaiian.
weekdayNamesHawaiian = []string{"Lāpule", "Po\u02bbakahi", "Po\u02bbalua", "Po\u02bbakolu", "Po\u02bbahā", "Po\u02bbalima", "Po\u02bbaono"}
// weekdayNamesHawaiianAbbr list the weekday name abbreviations in the
// Hawaiian.
weekdayNamesHawaiianAbbr = []string{"LP", "P1", "P2", "P3", "P4", "P5", "P6"}
// weekdayNamesHebrew list the weekday name in the Hebrew.
weekdayNamesHebrew = []string{
"\u05D9\u05D5\u05DD \u05E8\u05D0\u05E9\u05D5\u05DF",
"\u05D9\u05D5\u05DD \u05E9\u05E0\u05D9",
"\u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9",
"\u05D9\u05D5\u05DD \u05E8\u05D1\u05D9\u05E2\u05D9",
"\u05D9\u05D5\u05DD \u05D7\u05DE\u05D9\u05E9\u05D9",
"\u05D9\u05D5\u05DD \u05E9\u05D9\u05E9\u05D9",
"\u05E9\u05D1\u05EA",
}
// weekdayNamesHebrewAbbr list the weekday name abbreviations in the Hebrew.
weekdayNamesHebrewAbbr = []string{
"\u05D9\u05D5\u05DD \u05D0",
"\u05D9\u05D5\u05DD \u05D1",
"\u05D9\u05D5\u05DD \u05D2",
"\u05D9\u05D5\u05DD \u05D3",
"\u05D9\u05D5\u05DD \u05D4",
"\u05D9\u05D5\u05DD \u05D5",
"\u05E9\u05D1\u05EA",
}
// weekdayNamesHindi list the weekday name in the Hindi.
weekdayNamesHindi = []string{
"\u0930\u0935\u093F\u0935\u093E\u0930",
"\u0938\u094B\u092E\u0935\u093E\u0930",
"\u092E\u0902\u0917\u0932\u0935\u093E\u0930",
"\u092C\u0941\u0927\u0935\u093E\u0930",
"\u0917\u0941\u0930\u0941\u0935\u093E\u0930",
"\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930",
"\u0936\u0928\u093F\u0935\u093E\u0930",
}
// weekdayNamesHindiAbbr list the weekday name abbreviations in the Hindi.
weekdayNamesHindiAbbr = []string{
"\u0930\u0935\u093F.",
"\u0938\u094B\u092E.",
"\u092E\u0902\u0917\u0932.",
"\u092C\u0941\u0927.",
"\u0917\u0941\u0930\u0941.",
"\u0936\u0941\u0915\u094D\u0930.",
"\u0936\u0928\u093F.",
}
// weekdayNamesHungarian list the weekday name in the Hungarian.
weekdayNamesHungarian = []string{"vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat"}
// weekdayNamesHungarianAbbr list the weekday name abbreviations in the
// Hungarian.
weekdayNamesHungarianAbbr = []string{"V", "H", "K", "Sze", "Cs", "P", "Szo"}
// weekdayNamesIcelandic list the weekday name in the Icelandic.
weekdayNamesIcelandic = []string{"sunnudagur", "mánudagur", "þriðjudagur", "miðvikudagur", "fimmtudagur", "föstudagur", "laugardagur"}
// weekdayNamesIcelandicAbbr list the weekday name abbreviations in the
// Icelandic.
weekdayNamesIcelandicAbbr = []string{"sun.", "mán.", "þri.", "mið.", "fim.", "fös.", "lau."}
// weekdayNamesIgbo list the weekday name in the Igbo.
weekdayNamesIgbo = []string{"Ụbọchị Ụka", "Mọnde", "Tiuzdee", "Wenezdee", "Tọọzdee", "Fraịdee", "Satọdee"}
// weekdayNamesIgboAbbr list the weekday name abbreviations in the Igbo.
weekdayNamesIgboAbbr = []string{"Ụka", "Mọn", "Tiu", "Wen", "Tọọ", "Fraị", "Satọdee"}
// weekdayNamesIndonesian list the weekday name in the Indonesian.
weekdayNamesIndonesian = []string{"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"}
// weekdayNamesIndonesianAbbr list the weekday name abbreviations in the
// Indonesian.
weekdayNamesIndonesianAbbr = []string{"Mgg", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"}
// weekdayNamesInuktitut list the weekday name in the Inuktitut.
weekdayNamesInuktitut = []string{"Naattiinguja", "Naggajjau", "Aippiq", "Pingatsiq", "Sitammiq", "Tallirmiq", "Sivataarvik"}
// weekdayNamesInuktitutAbbr list the weekday name abbreviations in the
// Inuktitut.
weekdayNamesInuktitutAbbr = []string{"Nat", "Nag", "Aip", "Pi", "Sit", "Tal", "Siv"}
// weekdayNamesSyllabics list the weekday name in the Syllabics.
weekdayNamesSyllabics = []string{
"\u14C8\u1466\u144F\u1591\u152D",
"\u14C7\u14A1\u1490\u153E\u152D\u1405",
"\u140A\u1403\u1449\u1431\u1585",
"\u1431\u1593\u1466\u14EF\u1585",
"\u14EF\u1455\u14BB\u14A5\u1585",
"\u1455\u14EA\u14D5\u1550\u14A5\u1585",
"\u14EF\u1559\u1456\u1550\u1555\u1483",
}
// weekdayNamesSyllabicsAbbr list the weekday name abbreviations in the
// Syllabics.
weekdayNamesSyllabicsAbbr = []string{
"\u14C8\u1466\u144F",
"\u14C7\u14A1\u1490",
"\u140A\u1403\u1449\u1431",
"\u1431\u1593\u1466\u14EF",
"\u14EF\u1455",
"\u1455\u14EA\u14D5",
"\u14EF\u1559\u1456\u1550\u1555\u1483",
}
// weekdayNamesIrish list the weekday name in the Irish.
weekdayNamesIrish = []string{"Dé Domhnaigh", "Dé Luain", "Dé Máirt", "Dé Céadaoin", "Déardaoin", "Dé hAoine", "Dé Sathairn"}
// weekdayNamesIrishAbbr list the weekday name abbreviations in the Irish.
weekdayNamesIrishAbbr = []string{"Domh", "Luan", "Máirt", "Céad", "Déar", "Aoine", "Sath"}
// weekdayNamesItalian list the weekday name in the Italian.
weekdayNamesItalian = []string{"domenica", "lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato"}
// weekdayNamesItalianAbbr list the weekday name abbreviations in the
// Italian.
weekdayNamesItalianAbbr = []string{"dom", "lun", "mar", "mer", "gio", "ven", "sab"}
// weekdayNamesJapanese list the weekday name in the Japanese.
weekdayNamesJapanese = []string{"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"}
// weekdayNamesJapaneseAbbr list the weekday name abbreviations in the
// Japanese.
weekdayNamesJapaneseAbbr = []string{"日", "月", "火", "水", "木", "金", "土"}
// weekdayNamesKannada list the weekday name in the Kannada.
weekdayNamesKannada = []string{
"\u0CAD\u0CBE\u0CA8\u0CC1\u0CB5\u0CBE\u0CB0",
"\u0CB8\u0CCB\u0CAE\u0CB5\u0CBE\u0CB0",
"\u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0",
"\u0CAC\u0CC1\u0CA7\u0CB5\u0CBE\u0CB0",
"\u0C97\u0CC1\u0CB0\u0CC1\u0CB5\u0CBE\u0CB0",
"\u0CB6\u0CC1\u0C95\u0CCD\u0CB0\u0CB5\u0CBE\u0CB0",
"\u0CB6\u0CA8\u0CBF\u0CB5\u0CBE\u0CB0",
}
// weekdayNamesKannadaAbbr list the weekday name abbreviations in the
// Kannada.
weekdayNamesKannadaAbbr = []string{
"\u0CAD\u0CBE\u0CA8\u0CC1.",
"\u0CB8\u0CCB\u0CAE.",
"\u0CAE\u0C82\u0C97\u0CB3.",
"\u0CAC\u0CC1\u0CA7.",
"\u0C97\u0CC1\u0CB0\u0CC1.",
"\u0CB6\u0CC1\u0C95\u0CCD\u0CB0.",
"\u0CB6\u0CA8\u0CBF.",
}
// weekdayNamesKashmiri list the weekday name in the Kashmiri.
weekdayNamesKashmiri = []string{
"\u0627\u064E\u062A\u06BE\u0648\u0627\u0631",
"\u0698\u0654\u0646\u062F\u0631\u0655\u0631\u0648\u0627\u0631",
"\u0628\u06C6\u0645\u0648\u0627\u0631",
"\u0628\u0648\u062F\u0648\u0627\u0631",
"\u0628\u0631\u0620\u0633\u0648\u0627\u0631",
"\u062C\u064F\u0645\u06C1",
"\u0628\u0679\u0648\u0627\u0631",
}
// weekdayNamesKashmiriAbbr list the weekday name abbreviations in the
// Kashmiri.
weekdayNamesKashmiriAbbr = []string{
"\u0622\u062A\u06BE\u0648\u0627\u0631",
"\u0698\u0654\u0646\u062F\u0655\u0631\u0648\u0627\u0631",
"\u0628\u06C6\u0645\u0648\u0627\u0631",
"\u0628\u0648\u062F\u0648\u0627\u0631",
"\u0628\u0631\u0620\u0633\u0648\u0627\u0631",
"\u062C\u064F\u0645\u06C1",
"\u0628\u0679\u0648\u0627\u0631",
}
// weekdayNamesKazakh list the weekday name in the Kazakh.
weekdayNamesKazakh = []string{
"\u0436\u0435\u043A\u0441\u0435\u043D\u0431\u0456",
"\u0434\u04AF\u0439\u0441\u0435\u043D\u0431\u0456",
"\u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456",
"\u0441\u04D9\u0440\u0441\u0435\u043D\u0431\u0456",
"\u0431\u0435\u0439\u0441\u0435\u043D\u0431\u0456",
"\u0436\u04B1\u043C\u0430",
"\u0441\u0435\u043D\u0431\u0456",
}
// weekdayNamesKazakhAbbr list the weekday name abbreviations in the Kazakh.
weekdayNamesKazakhAbbr = []string{
"\u0436\u0435\u043A",
"\u0434\u04AF\u0439",
"\u0441\u0435\u0439",
"\u0441\u04D9\u0440",
"\u0431\u0435\u0439",
"\u0436\u04B1\u043C",
"\u0441\u0435\u043D",
}
// weekdayNamesKhmer list the weekday name in the Khmer.
weekdayNamesKhmer = []string{
"\u1790\u17D2\u1784\u17C3\u17A2\u17B6\u1791\u17B7\u178F\u17D2\u1799",
"\u1790\u17D2\u1784\u17C3\u1785\u17D0\u1793\u17D2\u1791",
"\u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A",
"\u1790\u17D2\u1784\u17C3\u1796\u17BB\u1792",
"\u1790\u17D2\u1784\u17C3\u1796\u17D2\u179A\u17A0\u179F\u17D2\u1794\u178F\u17B7\u17CD",
"\u1790\u17D2\u1784\u17C3\u179F\u17BB\u1780\u17D2\u179A",
"\u1790\u17D2\u1784\u17C3\u179F\u17C5\u179A\u17CD",
}
// weekdayNamesKhmerAbbr list the weekday name abbreviations in the Khmer.
weekdayNamesKhmerAbbr = []string{
"\u17A2\u17B6\u1791\u17B7.",
"\u1785.",
"\u17A2.",
"\u1796\u17BB",
"\u1796\u17D2\u179A\u17A0.",
"\u179F\u17BB.",
"\u179F.",
}
// weekdayNamesKiche list the weekday name in the Kiche.
weekdayNamesKiche = []string{"juq'ij", "kaq'ij", "oxq'ij", "kajq'ij", "joq'ij", "waqq'ij", "wuqq'ij"}
// weekdayNamesKicheAbbr list the weekday name abbreviations in the Kiche.
weekdayNamesKicheAbbr = []string{"juq'", "kaq'", "oxq'", "kajq'", "joq'", "waqq'", "wuqq'"}
// weekdayNamesKinyarwanda list the weekday name in the Kinyarwanda.
weekdayNamesKinyarwanda = []string{"Ku cyumweru", "Ku wa mbere", "Ku wa kabiri", "Ku wa gatatu", "Ku wa kane", "Ku wa gatanu", "Ku wa gatandatu"}
// weekdayNamesKinyarwandaAbbr list the weekday name abbreviations in the
// Kinyarwanda.
weekdayNamesKinyarwandaAbbr = []string{"cyu.", "mbe.", "kab.", "gat.", "kan.", "gnu.", "gat."}
// weekdayNamesKiswahili list the weekday name in the Kiswahili.
weekdayNamesKiswahili = []string{"Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi"}
// weekdayNamesKiswahiliAbbr list the weekday name abbreviations in the
// Kiswahili.
weekdayNamesKiswahiliAbbr = []string{"Jpl", "Jtt", "Jnn", "Jtn", "Alh", "Ijm", "Jms"}
// weekdayNamesKonkani list the weekday name in the Konkani.
weekdayNamesKonkani = []string{
"\u0906\u092F\u0924\u093E\u0930",
"\u0938\u094B\u092E\u093E\u0930",
"\u092E\u0902\u0917\u0933\u093E\u0930",
"\u092C\u0941\u0927\u0935\u093E\u0930",
"\u092C\u093F\u0930\u0947\u0938\u094D\u0924\u093E\u0930",
"\u0938\u0941\u0915\u094D\u0930\u093E\u0930",
"\u0936\u0947\u0928\u0935\u093E\u0930",
}
// weekdayNamesKonkaniAbbr list the weekday name abbreviations in the
// Konkani.
weekdayNamesKonkaniAbbr = []string{
"\u0906\u092F.",
"\u0938\u094B\u092E.",
"\u092E\u0902\u0917\u0933.",
"\u092C\u0941\u0927.",
"\u092C\u093F\u0930\u0947.",
"\u0938\u0941\u0915\u094D\u0930.",
"\u0936\u0947\u0928.",
}
// weekdayNamesKorean list the weekday name in the Korean.
weekdayNamesKorean = []string{"일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"}
// weekdayNamesKoreanAbbr list the weekday name abbreviations in the Korean.
weekdayNamesKoreanAbbr = []string{"일", "월", "화", "수", "목", "금", "토"}
// weekdayNamesKyrgyz list the weekday name in the Kyrgyz.
weekdayNamesKyrgyz = []string{
"\u0436\u0435\u043A\u0448\u0435\u043C\u0431\u0438",
"\u0434\u04AF\u0439\u0448\u04E9\u043C\u0431\u04AF",
"\u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438",
"\u0448\u0430\u0440\u0448\u0435\u043C\u0431\u0438",
"\u0431\u0435\u0439\u0448\u0435\u043C\u0431\u0438",
"\u0436\u0443\u043C\u0430",
"\u0438\u0448\u0435\u043C\u0431\u0438",
}
// weekdayNamesKyrgyzAbbr list the weekday name abbreviations in the Kyrgyz.
weekdayNamesKyrgyzAbbr = []string{
"\u0436\u0435\u043A.",
"\u0434\u04AF\u0439.",
"\u0448\u0435\u0439\u0448.",
"\u0448\u0430\u0440\u0448.",
"\u0431\u0435\u0439\u0448.",
"\u0436\u0443\u043C\u0430",
"\u0438\u0448\u043C.",
}
// weekdayNamesLao list the weekday name in the Lao.
weekdayNamesLao = []string{
"\u0EA7\u0EB1\u0E99\u0EAD\u0EB2\u0E97\u0EB4\u0E94",
"\u0EA7\u0EB1\u0E99\u0E88\u0EB1\u0E99",
"\u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99",
"\u0EA7\u0EB1\u0E99\u0E9E\u0EB8\u0E94",
"\u0EA7\u0EB1\u0E99\u0E9E\u0EB0\u0EAB\u0EB1\u0E94",
"\u0EA7\u0EB1\u0E99\u0EAA\u0EB8\u0E81",
"\u0EA7\u0EB1\u0E99\u0EC0\u0EAA\u0EBB\u0EB2",
}
// weekdayNamesLaoAbbr list the weekday name abbreviations in the Lao.
weekdayNamesLaoAbbr = []string{
"\u0EAD\u0EB2\u0E97\u0EB4\u0E94",
"\u0E88\u0EB1\u0E99",
"\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99",
"\u0E9E\u0EB8\u0E94",
"\u0E9E\u0EB0\u0EAB\u0EB1\u0E94",
"\u0EAA\u0EB8\u0E81",
"\u0EC0\u0EAA\u0EBB\u0EB2",
}
// weekdayNamesLatin list the weekday name in the Latin.
weekdayNamesLatin = []string{"Solis", "Lunae", "Martis", "Mercurii", "Jovis", "Veneris", "Saturni"}
// weekdayNamesLatinAbbr list the weekday name abbreviations in the Latin.
weekdayNamesLatinAbbr = []string{"Sol", "Lun", "Mar", "Mer", "Jov", "Ven", "Sat"}
// weekdayNamesLatvian list the weekday name in the Latvian.
weekdayNamesLatvian = []string{"svētdiena", "pirmdiena", "otrdiena", "trešdiena", "ceturtdiena", "piektdiena", "sestdiena"}
// weekdayNamesLatvianAbbr list the weekday name abbreviations in the
// Latvian.
weekdayNamesLatvianAbbr = []string{"svētd.", "pirmd.", "otrd.", "trešd.", "ceturtd.", "piektd.", "sestd."}
// weekdayNamesLithuanian list the weekday name in the Lithuanian.
weekdayNamesLithuanian = []string{"sekmadienis", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis"}
// weekdayNamesLithuanianAbbr list the weekday name abbreviations in the
// Lithuanian.
weekdayNamesLithuanianAbbr = []string{"sk", "pr", "an", "tr", "kt", "pn", "št"}
// weekdayNamesLowerSorbian list the weekday name in the Lower Sorbian.
weekdayNamesLowerSorbian = []string{"njeźela", "ponjeźele", "wałtora", "srjoda", "stwórtk", "pětk", "sobota"}
// weekdayNamesLowerSorbianAbbr list the weekday name abbreviations in the
// Lower Sorbian.
weekdayNamesLowerSorbianAbbr = []string{"nje", "pon", "wa\u0142", "srj", "stw", "p\u011Bt", "sob"}
// weekdayNamesLuxembourgish list the weekday name in the Luxembourgish.
weekdayNamesLuxembourgish = []string{"Sonndeg", "Méindeg", "Dënschdeg", "Mëttwoch", "Donneschdeg", "Freideg", "Samschdeg"}
// weekdayNamesLuxembourgishAbbr list the weekday name abbreviations in the
// Luxembourgish.
weekdayNamesLuxembourgishAbbr = []string{"Son", "Méi", "Dën", "Mët", "Don", "Fre", "Sam"}
// weekdayNamesMacedonian list the weekday name in the Macedonian.
weekdayNamesMacedonian = []string{
"\u043D\u0435\u0434\u0435\u043B\u0430",
"\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u043D\u0438\u043A",
"\u0432\u0442\u043E\u0440\u043D\u0438\u043A",
"\u0441\u0440\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0440\u0442\u043E\u043A",
"\u043F\u0435\u0442\u043E\u043A",
"\u0441\u0430\u0431\u043E\u0442\u0430",
}
// weekdayNamesMacedonianAbbr list the weekday name abbreviations in the
// Macedonian.
weekdayNamesMacedonianAbbr = []string{
"\u043D\u0435\u0434.",
"\u043F\u043E\u043D.",
"\u0432\u0442.",
"\u0441\u0440\u0435.",
"\u0447\u0435\u0442.",
"\u043F\u0435\u0442.",
"\u0441\u0430\u0431.",
}
// weekdayNamesMalay list the weekday name in the Malay.
weekdayNamesMalay = []string{"Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu"}
// weekdayNamesMalayAbbr list the weekday name abbreviations in the Lower
// Sorbian.
weekdayNamesMalayAbbr = []string{"Ahd", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab"}
// weekdayNamesMalayalam list the weekday name in the Malayalam.
weekdayNamesMalayalam = []string{
"\u0D1E\u0D3E\u0D2F\u0D31\u0D3E\u0D34\u0D4D\u200C\u0D1A",
"\u0D24\u0D3F\u0D19\u0D4D\u0D15\u0D33\u0D3E\u0D34\u0D4D\u200C\u0D1A",
"\u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A",
"\u0D2C\u0D41\u0D27\u0D28\u0D3E\u0D34\u0D4D\u200C\u0D1A",
"\u0D35\u0D4D\u0D2F\u0D3E\u0D34\u0D3E\u0D34\u0D4D\u200C\u0D1A",
"\u0D35\u0D46\u0D33\u0D4D\u0D33\u0D3F\u0D2F\u0D3E\u0D34\u0D4D\u200C\u0D1A",
"\u0D36\u0D28\u0D3F\u0D2F\u0D3E\u0D34\u0D4D\u200C\u0D1A",
}
// weekdayNamesMalayalamAbbr list the weekday name abbreviations in the
// Malayalam.
weekdayNamesMalayalamAbbr = []string{
"\u0D1E\u0D3E\u0D2F\u0D7C",
"\u0D24\u0D3F\u0D19\u0D4D\u0D15\u0D7E",
"\u0D1A\u0D4A\u0D35\u0D4D\u0D35",
"\u0D2C\u0D41\u0D27\u0D7B",
"\u0D35\u0D4D\u0D2F\u0D3E\u0D34\u0D02",
"\u0D35\u0D46\u0D33\u0D4D\u0D33\u0D3F",
"\u0D36\u0D28\u0D3F",
}
// weekdayNamesMaltese list the weekday name in the Maltese.
weekdayNamesMaltese = []string{"Il-\u0126add", "It-Tnejn", "It-Tlieta", "L-Erbg\u0127a", "Il-\u0126amis", "Il-\u0120img\u0127a", "Is-Sibt"}
// weekdayNamesMalteseAbbr list the weekday name abbreviations in the
// Maltese.
weekdayNamesMalteseAbbr = []string{"\u0126ad", "Tne", "Tli", "Erb", "\u0126am", "\u0120im", "Sib"}
// weekdayNamesMaori list the weekday name in the Maori.
weekdayNamesMaori = []string{"Rātapu", "Rāhina", "Rātū", "Rāapa", "Rāpare", "Rāmere", "Rāhoroi"}
// weekdayNamesMaoriAbbr list the weekday name abbreviations in the Maori.
weekdayNamesMaoriAbbr = []string{"Ta", "Hi", "Tū", "Apa", "Pa", "Me", "Ho"}
// weekdayNamesMapudungun list the weekday name in the Mapudungun.
weekdayNamesMapudungun = []string{"Kiñe Ante", "Epu Ante", "Kila Ante", "Meli Ante", "Kechu Ante", "Cayu Ante", "Regle Ante"}
// weekdayNamesMapudungunAbbr list the weekday name abbreviations in the
// Mapudungun.
weekdayNamesMapudungunAbbr = []string{"Kiñe", "Epu", "Kila", "Meli", "Kechu", "Cayu", "Regle"}
// weekdayNamesMarathi list the weekday name in the Marathi.
weekdayNamesMarathi = []string{
"\u0930\u0935\u093F\u0935\u093E\u0930",
"\u0938\u094B\u092E\u0935\u093E\u0930",
"\u092E\u0902\u0917\u0933\u0935\u093E\u0930",
"\u092C\u0941\u0927\u0935\u093E\u0930",
"\u0917\u0941\u0930\u0941\u0935\u093E\u0930",
"\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930",
"\u0936\u0928\u093F\u0935\u093E\u0930",
}
// weekdayNamesMarathiAbbr list the weekday name abbreviations in the
// Marathi.
weekdayNamesMarathiAbbr = []string{
"\u0930\u0935\u093F.",
"\u0938\u094B\u092E.",
"\u092E\u0902\u0917\u0933.",
"\u092C\u0941\u0927.",
"\u0917\u0941\u0930\u0941.",
"\u0936\u0941\u0915\u094D\u0930.",
"\u0936\u0928\u093F.",
}
// weekdayNamesMohawk list the weekday name in the Mohawk.
weekdayNamesMohawk = []string{"Awentatokentì:ke", "Awentataón'ke", "Ratironhia'kehronòn:ke", "Soséhne", "Okaristiiáhne", "Ronwaia'tanentaktonhne", "Entákta"}
// weekdayNamesMongolian list the weekday name in the Mongolian.
weekdayNamesMongolian = []string{
"\u043D\u044F\u043C",
"\u0434\u0430\u0432\u0430\u0430",
"\u043C\u044F\u0433\u043C\u0430\u0440",
"\u043B\u0445\u0430\u0433\u0432\u0430",
"\u043F\u04AF\u0440\u044D\u0432",
"\u0431\u0430\u0430\u0441\u0430\u043D",
"\u0431\u044F\u043C\u0431\u0430",
}
// weekdayNamesMongolianAbbr list the weekday name abbreviations in the
// Mongolian.
weekdayNamesMongolianAbbr = []string{
"\u041D\u044F",
"\u0414\u0430",
"\u041C\u044F",
"\u041B\u0445",
"\u041F\u04AF",
"\u0411\u0430",
"\u0411\u044F",
}
// weekdayNamesMongolianCyrlAbbr list the weekday name abbreviations in the
// Mongolian (Cyrillic).
weekdayNamesMongolianCyrlAbbr = []string{
"\u041D\u044F",
"\u0414\u0430",
"\u041C\u044F",
"\u041B\u0445\u0430",
"\u041F\u04AF",
"\u0411\u0430",
"\u0411\u044F",
}
// weekdayNamesTraditionalMongolian list the weekday name abbreviations in
// the Traditional Mongolian.
weekdayNamesTraditionalMongolian = []string{
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1821\u1833\u1826\u1837",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1828\u1822\u182D\u1821\u1828",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u182C\u1823\u1836\u1820\u1837",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u182D\u1824\u1837\u182A\u1820\u1828",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1832\u1820\u182A\u1824\u1828",
"\u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828",
}
// weekdayNamesTraditionalMongolianMN list the weekday name abbreviations in
// the Traditional Mongolian MN.
weekdayNamesTraditionalMongolianMN = []string{
"\u1828\u1822\u182E\u180E\u1820",
"\u1833\u1820\u1838\u1820",
"\u182E\u1822\u182D\u182E\u1820\u1837",
"\u1840\u1820\u182D\u182A\u1820",
"\u182B\u1826\u1837\u182A\u1826",
"\u182A\u1820\u1830\u1820\u1829",
"\u182A\u1822\u182E\u182A\u1820",
}
// weekdayNamesNepali list the weekday name in the Nepali.
weekdayNamesNepali = []string{
"\u0906\u0907\u0924\u0935\u093E\u0930",
"\u0938\u094B\u092E\u0935\u093E\u0930",
"\u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930",
"\u092C\u0941\u0927\u0935\u093E\u0930",
"\u092C\u093F\u0939\u0940\u0935\u093E\u0930",
"\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0930",
"\u0936\u0928\u093F\u0935\u093E\u0930",
}
// weekdayNamesNepaliAbbr list the weekday name abbreviations in the Nepali.
weekdayNamesNepaliAbbr = []string{
"\u0906\u0907\u0924",
"\u0938\u094B\u092E",
"\u092E\u0919\u094D\u0917\u0932",
"\u092C\u0941\u0927",
"\u092C\u093F\u0939\u0940",
"\u0936\u0941\u0915\u094D\u0930",
"\u0936\u0928\u093F",
}
// weekdayNamesNepaliIN list the weekday name in the Nepali India.
weekdayNamesNepaliIN = []string{
"\u0906\u0907\u0924\u092C\u093E\u0930",
"\u0938\u094B\u092E\u092C\u093E\u0930",
"\u092E\u0919\u094D\u0917\u0932\u092C\u093E\u0930",
"\u092C\u0941\u0927\u092C\u093E\u0930",
"\u092C\u093F\u0939\u093F\u092C\u093E\u0930",
"\u0936\u0941\u0915\u094D\u0930\u092C\u093E\u0930",
"\u0936\u0928\u093F\u092C\u093E\u0930",
}
// weekdayNamesNepaliINAbbr list the weekday name abbreviations in the
// Nepali India.
weekdayNamesNepaliINAbbr = []string{
"\u0906\u0907\u0924",
"\u0938\u094B\u092E",
"\u092E\u0919\u094D\u0917\u0932",
"\u092C\u0941\u0927",
"\u092C\u093F\u0939\u093F",
"\u0936\u0941\u0915\u094D\u0930",
"\u0936\u0928\u093F",
}
// weekdayNamesNorwegian list the weekday name in the Norwegian.
weekdayNamesNorwegian = []string{"søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"}
// weekdayNamesNorwegianAbbr list the weekday name abbreviations in the
// Norwegian.
weekdayNamesNorwegianAbbr = []string{"søn.", "man.", "tir.", "ons.", "tor.", "fre.", "lør."}
// weekdayNamesNorwegianNOAbbr list the weekday name abbreviations in the
// Norwegian Norway.
weekdayNamesNorwegianNOAbbr = []string{"søn", "man", "tir", "ons", "tor", "fre", "lør"}
// weekdayNamesNorwegianNynorsk list the weekday name abbreviations in the
// Norwegian Nynorsk.
weekdayNamesNorwegianNynorsk = []string{"søndag", "måndag", "tysdag", "onsdag", "torsdag", "fredag", "laurdag"}
// weekdayNamesNorwegianNynorskAbbr list the weekday name abbreviations in
// the Norwegian Nynorsk.
weekdayNamesNorwegianNynorskAbbr = []string{"søn", "mån", "tys", "ons", "tor", "fre", "lau"}
// weekdayNamesOccitan list the weekday name abbreviations in the Occitan.
weekdayNamesOccitan = []string{"dimenge", "diluns", "dimarts", "dimècres", "dijòus", "divendres", "dissabte"}
// weekdayNamesOccitanAbbr list the weekday name abbreviations in the
// Occitan.
weekdayNamesOccitanAbbr = []string{"dg.", "dl.", "dma.", "dmc.", "dj.", "dv.", "ds."}
// weekdayNamesOdia list the weekday name in the Odia.
weekdayNamesOdia = []string{
"\u0B30\u0B2C\u0B3F\u0B2C\u0B3E\u0B30",
"\u0B38\u0B4B\u0B2E\u0B2C\u0B3E\u0B30",
"\u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30",
"\u0B2C\u0B41\u0B27\u0B2C\u0B3E\u0B30",
"\u0B17\u0B41\u0B30\u0B41\u0B2C\u0B3E\u0B30",
"\u0B36\u0B41\u0B15\u0B4D\u0B30\u0B2C\u0B3E\u0B30",
"\u0B36\u0B28\u0B3F\u0B2C\u0B3E\u0B30",
}
// weekdayNamesOdiaAbbr list the weekday name abbreviations in the Odia.
weekdayNamesOdiaAbbr = []string{
"\u0B30\u0B2C\u0B3F.",
"\u0B38\u0B4B\u0B2E.",
"\u0B2E\u0B19\u0B4D\u0B17\u0B33.",
"\u0B2C\u0B41\u0B27.",
"\u0B17\u0B41\u0B30\u0B41.",
"\u0B36\u0B41\u0B15\u0B4D\u0B30.",
"\u0B36\u0B28\u0B3F.",
}
// weekdayNamesOromo list the weekday name abbreviations in the Oromo.
weekdayNamesOromo = []string{"Dilbata", "Wiixata", "Qibxata", "Roobii", "Kamiisa", "Jimaata", "Sanbata"}
// weekdayNamesOromoAbbr list the weekday name abbreviations in the Oromo.
weekdayNamesOromoAbbr = []string{"Dil", "Wix", "Qib", "Rob", "Kam", "Jim", "San"}
// weekdayNamesPashto list the weekday name in the Pashto.
weekdayNamesPashto = []string{
"\u064A\u0648\u0646\u06CD",
"\u062F\u0648\u0646\u06CD",
"\u062F\u0631\u06D0\u0646\u06CD",
"\u0685\u0644\u0631\u0646\u06CD",
"\u067E\u064A\u0646\u0681\u0646\u06CD",
"\u062C\u0645\u0639\u0647",
"\u0627\u0648\u0646\u06CD",
}
// weekdayNamesPersian list the weekday name in the Persian.
weekdayNamesPersian = []string{
"\u064A\u0643\u0634\u0646\u0628\u0647",
"\u062F\u0648\u0634\u0646\u0628\u0647",
"\u0633\u0647 \u0634\u0646\u0628\u0647",
"\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647",
"\u067E\u0646\u062C\u0634\u0646\u0628\u0647",
"\u062C\u0645\u0639\u0647",
"\u0634\u0646\u0628\u0647",
}
// weekdayNamesPolish list the weekday name abbreviations in the Polish.
weekdayNamesPolish = []string{"niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"}
// weekdayNamesPolishAbbr list the weekday name abbreviations in the Polish.
weekdayNamesPolishAbbr = []string{"niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob."}
// weekdayNamesPortuguese list the weekday name abbreviations in the
// Portuguese.
weekdayNamesPortuguese = []string{"domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado"}
// weekdayNamesPortugueseAbbr list the weekday name abbreviations in the
// Portuguese.
weekdayNamesPortugueseAbbr = []string{"dom", "seg", "ter", "qua", "qui", "sex", "sáb"}
// weekdayNamesPunjabi list the weekday name in the Punjabi.
weekdayNamesPunjabi = []string{
"\u0A10\u0A24\u0A35\u0A3E\u0A30",
"\u0A38\u0A4B\u0A2E\u0A35\u0A3E\u0A30",
"\u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30",
"\u0A2C\u0A41\u0A71\u0A27\u0A35\u0A3E\u0A30",
"\u0A35\u0A40\u0A30\u0A35\u0A3E\u0A30",
"\u0A38\u0A3C\u0A41\u0A71\u0A15\u0A30\u0A35\u0A3E\u0A30",
"\u0A38\u0A3C\u0A28\u0A3F\u0A71\u0A1A\u0A30\u0A35\u0A3E\u0A30",
}
// weekdayNamesPunjabiAbbr list the weekday name abbreviations in the
// Punjabi.
weekdayNamesPunjabiAbbr = []string{
"\u0A10\u0A24.",
"\u0A38\u0A4B\u0A2E.",
"\u0A2E\u0A70\u0A17\u0A32.",
"\u0A2C\u0A41\u0A71\u0A27.",
"\u0A35\u0A40\u0A30.",
"\u0A38\u0A3C\u0A41\u0A15\u0A30.",
"\u0A38\u0A3C\u0A28\u0A3F\u0A71\u0A1A\u0A30.",
}
// weekdayNamesPunjabiArab list the weekday name in the Punjabi Arab.
weekdayNamesPunjabiArab = []string{
"\u067E\u064A\u0631",
"\u0645\u0646\u06AF\u0644",
"\u0628\u062F\u06BE",
"\u062C\u0645\u0639\u0631\u0627\u062A",
"\u062C\u0645\u0639\u0647",
"\u0647\u0641\u062A\u0647",
"\u0627\u062A\u0648\u0627\u0631",
}
// weekdayNamesQuechua list the weekday name abbreviations in the Quechua.
weekdayNamesQuechua = []string{"intichaw", "killachaw", "atipachaw", "quyllurchaw", "Ch' askachaw", "Illapachaw", "k'uychichaw"}
// weekdayNamesQuechuaAbbr list the weekday name abbreviations in the Quechua.
weekdayNamesQuechuaAbbr = []string{"int", "kil", "ati", "quy", "Ch'", "Ill", "k'u"}
// weekdayNamesQuechuaEcuador list the weekday name abbreviations in the
// Quechua Ecuador.
weekdayNamesQuechuaEcuador = []string{"inti", "awaki", "wanra", "chillay", "kullka", "chaska", "wakma"}
// weekdayNamesQuechuaEcuadorAbbr list the weekday name abbreviations in the
// Quechua Ecuador.
weekdayNamesQuechuaEcuadorAbbr = []string{"int", "awk", "wan", "chy", "kuk", "cha", "wak"}
// weekdayNamesQuechuaPeru list the weekday name abbreviations in the
// Quechua Peru.
weekdayNamesQuechuaPeru = []string{"Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"}
// weekdayNamesQuechuaPeruAbbr list the weekday name abbreviations in the
// Quechua Peru.
weekdayNamesQuechuaPeruAbbr = []string{"Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sab"}
// weekdayNamesRomanian list the weekday name abbreviations in the Romanian.
weekdayNamesRomanian = []string{"duminică", "luni", "marți", "miercuri", "joi", "vineri", "sâmbătă"}
// weekdayNamesRomanianAbbr list the weekday name abbreviations in the
// Romanian.
weekdayNamesRomanianAbbr = []string{"dum.", "lun.", "mar.", "mie.", "joi", "vin.", "sâm."}
// weekdayNamesRomanianMoldovaAbbr list the weekday name abbreviations in
// the Romanian Moldova.
weekdayNamesRomanianMoldovaAbbr = []string{"Du", "Lu", "Mar", "Mie", "Jo", "Vi", "Sâ"}
// weekdayNamesRomansh list the weekday name abbreviations in the Romansh.
weekdayNamesRomansh = []string{"dumengia", "glindesdi", "mardi", "mesemna", "gievgia", "venderdi", "sonda"}
// weekdayNamesRomanshAbbr list the weekday name abbreviations in the
// Romansh.
weekdayNamesRomanshAbbr = []string{"du", "gli", "ma", "me", "gie", "ve", "so"}
// weekdayNamesRussian list the weekday name abbreviations in the Russian.
weekdayNamesRussian = []string{
"\u0432\u043E\u0441\u043A\u0440\u0435\u0441\u0435\u043D\u044C\u0435",
"\u043F\u043E\u043D\u0435\u0434\u0435\u043B\u044C\u043D\u0438\u043A",
"\u0432\u0442\u043E\u0440\u043D\u0438\u043A",
"\u0441\u0440\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0435\u0440\u0433",
"\u043F\u044F\u0442\u043D\u0438\u0446\u0430",
"\u0441\u0443\u0431\u0431\u043E\u0442\u0430",
}
// weekdayNamesRussianAbbr list the weekday name abbreviations in the
// Russian.
weekdayNamesRussianAbbr = []string{
"\u0412\u0441",
"\u041F\u043D",
"\u0412\u0442",
"\u0421\u0440",
"\u0427\u0442",
"\u041F\u0442",
"\u0421\u0431",
}
// weekdayNamesSakha list the weekday name abbreviations in the Sakha.
weekdayNamesSakha = []string{
"\u04E8\u0440\u04E9\u0431\u04AF\u043B",
"\u044D\u043D\u0438\u0434\u0438\u044D\u043D\u043D\u044C\u0438\u043A",
"\u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A",
"\u0421\u044D\u0440\u044D\u0434\u044D\u044D",
"\u0427\u044D\u043F\u043F\u0438\u044D\u0440",
"\u0411\u044D\u044D\u0442\u0438\u043D\u0441\u044D",
"\u0421\u0443\u0431\u0443\u043E\u0442\u0430",
}
// weekdayNamesSakhaAbbr list the weekday name abbreviations in the Sakha.
weekdayNamesSakhaAbbr = []string{
"\u04E8\u0440",
"\u0431\u043D",
"\u043E\u043F",
"\u0441\u044D",
"\u0447\u043F",
"\u0431\u044D",
"\u0441\u0431",
}
// weekdayNamesSami list the weekday name abbreviations in the Sami.
weekdayNamesSami = []string{"pasepeivi", "vuossargâ", "majebargâ", "koskokko", "tuorâstâh", "vástuppeivi", "lávurdâh"}
// weekdayNamesSamiAbbr list the weekday name abbreviations in the Sami.
weekdayNamesSamiAbbr = []string{"pas", "vuo", "maj", "kos", "tuo", "vás", "láv"}
// weekdayNamesSamiSamiLule list the weekday name abbreviations in the Sami
// (SamiLule).
weekdayNamesSamiSamiLule = []string{"ájllek", "mánnodahka", "dijstahka", "gasskavahkko", "duorastahka", "bierjjedahka", "lávvodahka"}
// weekdayNamesSamiSamiLuleAbbr list the weekday name abbreviations in the
// Sami (SamiLule).
weekdayNamesSamiSamiLuleAbbr = []string{"ájl", "mán", "dis", "gas", "duor", "bier", "láv"}
// weekdayNamesSamiSweden list the weekday name abbreviations in the Sami
// (Lule) Sweden.
weekdayNamesSamiSweden = []string{"sådnåbiejvve", "mánnodahka", "dijstahka", "gasskavahkko", "duorastahka", "bierjjedahka", "lávvodahka"}
// weekdayNamesSamiSwedenAbbr list the weekday name abbreviations in the
// Sami (Lule) Sweden.
weekdayNamesSamiSwedenAbbr = []string{"såd", "mán", "dis", "gas", "duor", "bier", "láv"}
// weekdayNamesSamiNorthern list the weekday name abbreviations in the Sami
// (Northern).
weekdayNamesSamiNorthern = []string{"sotnabeaivi", "vuossárga", "maŋŋebárga", "gaskavahkku", "duorasdat ", "bearjadat", "lávvardat"}
// weekdayNamesSamiNorthernFIAbbr list the weekday name abbreviations in the
// Sami (Northern).
weekdayNamesSamiNorthernAbbr = []string{"sotn", "vuos", "maŋ", "gask", "duor", "bear", "láv"}
// weekdayNamesSamiNorthernFI list the weekday name abbreviations in the
// Sami (Northern) Finland.
weekdayNamesSamiNorthernFI = []string{"sotnabeaivi", "vuossárga", "maŋŋebárga", "gaskavahkku", "duorastat", "bearjadat", "lávvardat"}
// weekdayNamesSamiNorthernFIAbbr list the weekday name abbreviations in the
// Sami (Northern) Finland.
weekdayNamesSamiNorthernFIAbbr = []string{"so", "má", "di", "ga", "du", "be", "lá"}
// weekdayNamesSamiNorthernSE list the weekday name abbreviations in the
// Sami (Northern) Sweden.
weekdayNamesSamiNorthernSE = []string{"sotnabeaivi", "mánnodat", "disdat", "gaskavahkku", "duorastat", "bearjadat", "lávvardat"}
// weekdayNamesSamiNorthernSEAbbr list the weekday name abbreviations in the
// Sami (Northern) Sweden.
weekdayNamesSamiNorthernSEAbbr = []string{"sotn", "mán", "dis", "gask", "duor", "bear", "láv"}
// weekdayNamesSamiSkolt list the weekday name abbreviations in the Sami
// (Skolt).
weekdayNamesSamiSkolt = []string{"pâ\u00B4sspei\u00B4vv", "vuõssargg", "mââibargg", "seärad", "neljdpei\u00B4vv", "piâtnâc", "sue\u00B4vet"}
// weekdayNamesSamiSkoltAbbr list the weekday name abbreviations in the Sami
// (Skolt).
weekdayNamesSamiSkoltAbbr = []string{"pâ", "vu", "mâ", "se", "ne", "pi", "su"}
// weekdayNamesSamiSouthern list the weekday name abbreviations in the Sami
// (Southern).
weekdayNamesSamiSouthern = []string{"aejlege", "måanta", "dæjsta", "gaskevåhkoe", "duarsta", "bearjadahke", "laavvardahke"}
// weekdayNamesSamiSouthernAbbr list the weekday name abbreviations in the
// Sami (Southern).
weekdayNamesSamiSouthernAbbr = []string{"aej", "måa", "dæj", "gask", "duar", "bearj", "laav"}
// weekdayNamesSanskrit list the weekday name abbreviations in the Sanskrit.
weekdayNamesSanskrit = []string{
"\u0930\u0935\u093F\u0935\u093E\u0938\u0930\u0903",
"\u0938\u094B\u092E\u0935\u093E\u0938\u0930\u0903",
"\u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903",
"\u092C\u0941\u0927\u0935\u093E\u0938\u0930\u0903",
"\u0917\u0941\u0930\u0941\u0935\u093E\u0938\u0930:",
"\u0936\u0941\u0915\u094D\u0930\u0935\u093E\u0938\u0930\u0903",
"\u0936\u0928\u093F\u0935\u093E\u0938\u0930\u0903",
}
// weekdayNamesSanskritAbbr list the weekday name abbreviations in the
// Sanskrit.
weekdayNamesSanskritAbbr = []string{
"\u0930\u0935\u093F",
"\u0938\u094B\u092E",
"\u092E\u0919\u094D\u0917",
"\u092C\u0941\u0927",
"\u0917\u0941\u0930\u0941",
"\u0936\u0941\u0915\u094D\u0930",
"\u0936\u0928\u093F",
}
// weekdayNamesGaelic list the weekday name abbreviations in the Gaelic.
weekdayNamesGaelic = []string{"DiDòmhnaich", "DiLuain", "DiMàirt", "DiCiadain", "DiarDaoin", "DihAoine", "DiSathairne"}
// weekdayNamesGaelicAbbr list the weekday name abbreviations in the Gaelic.
weekdayNamesGaelicAbbr = []string{"DiD", "DiL", "DiM", "DiC", "Dia", "Dih", "DiS"}
// weekdayNamesSerbian list the weekday name abbreviations in the Serbian.
weekdayNamesSerbian = []string{
"\u043D\u0435\u0434\u0435\u0459\u0430",
"\u043F\u043E\u043D\u0435\u0434\u0435\u0459\u0430\u043A",
"\u0443\u0442\u043E\u0440\u0430\u043A",
"\u0441\u0440\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A",
"\u043F\u0435\u0442\u0430\u043A",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesSerbianAbbr list the weekday name abbreviations in the
// Serbian.
weekdayNamesSerbianAbbr = []string{
"\u043D\u0435\u0434.",
"\u043F\u043E\u043D.",
"\u0443\u0442.",
"\u0441\u0440.",
"\u0447\u0435\u0442.",
"\u043F\u0435\u0442.",
"\u0441\u0443\u0431.",
}
// weekdayNamesSerbianBA list the weekday name abbreviations in the Serbian
// (Cyrillic) Bosnia and Herzegovina.
weekdayNamesSerbianBA = []string{
"\u043D\u0435\u0434\u0458\u0435\u0459\u0430",
"\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A",
"\u0443\u0442\u043E\u0440\u0430\u043A",
"\u0441\u0440\u0438\u0458\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A",
"\u043F\u0435\u0442\u0430\u043A",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesSerbianBAAbbr list the weekday name abbreviations in the
// Serbian (Cyrillic) Bosnia and Herzegovina.
weekdayNamesSerbianBAAbbr = []string{
"\u043D\u0435\u0434",
"\u043F\u043E\u043D",
"\u0443\u0442\u043E",
"\u0441\u0440\u0438",
"\u0447\u0435\u0442",
"\u043F\u0435\u0442",
"\u0441\u0443\u0431",
}
// weekdayNamesSerbianLatin list the weekday name abbreviations in the
// Serbian (Latin).
weekdayNamesSerbianLatin = []string{"nedelja", "ponedeljak", "utorak", "sreda", "četvrtak", "petak", "subota"}
// weekdayNamesSerbianLatinAbbr list the weekday name abbreviations in the
// Serbian (Latin).
weekdayNamesSerbianLatinAbbr = []string{"ned", "pon", "uto", "sre", "čet", "pet", "sub"}
// weekdayNamesSerbianLatinBA list the weekday name abbreviations in the
// Serbian (Latin) Bosnia and Herzegovina.
weekdayNamesSerbianLatinBA = []string{"nedjelja", "ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"}
// weekdayNamesSerbianLatinBAAbbr list the weekday name abbreviations in the
// Serbian (Latin) Bosnia and Herzegovina.
weekdayNamesSerbianLatinBAAbbr = []string{"ned", "pon", "uto", "sri", "čet", "pet", "sub"}
// weekdayNamesSerbianLatinCSAbbr list the weekday name abbreviations in the
// Serbian (Latin) Serbia and Montenegro (Former).
weekdayNamesSerbianLatinCSAbbr = []string{"ned.", "pon.", "uto.", "sre.", "čet.", "pet.", "sub."}
// weekdayNamesSerbianLatinME list the weekday name abbreviations in the
// Serbian (Latin) Montenegro.
weekdayNamesSerbianLatinME = []string{"nedjelja", "ponedeljak", "utorak", "srijeda", "četvrtak", "petak", "subota"}
// weekdayNamesSerbianME list the weekday name abbreviations in the Serbian
// (Cyrillic) Montenegro.
weekdayNamesSerbianME = []string{
"\u043D\u0435\u0434\u0435\u0459\u0430",
"\u043F\u043E\u043D\u0435\u0434\u0458\u0435\u0459\u0430\u043A",
"\u0443\u0442\u043E\u0440\u0430\u043A",
"\u0441\u0440\u0438\u0458\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043A",
"\u043F\u0435\u0442\u0430\u043A",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesSesothoSaLeboa list the weekday name abbreviations in the
// Sesotho sa Leboa.
weekdayNamesSesothoSaLeboa = []string{"Lamorena", "Musopologo", "Labobedi", "Laboraro", "Labone", "Labohlano", "Mokibelo"}
// weekdayNamesSesothoSaLeboaAbbr list the weekday name abbreviations in the
// Sesotho sa Leboa.
weekdayNamesSesothoSaLeboaAbbr = []string{"Lam", "Moš", "Lbb", "Lbr", "Lbn", "Lbh", "Mok"}
// weekdayNamesSetswana list the weekday name abbreviations in the Setswana.
weekdayNamesSetswana = []string{"Sontaga", "Mosopulogo", "Labobedi", "Laboraro", "Labone", "Labotlhano", "Matlhatso"}
// weekdayNamesSetswanaAbbr list the weekday name abbreviations in the
// Setswana.
weekdayNamesSetswanaAbbr = []string{"Sont.", "Mos.", "Lab.", "Labr.", "Labn.", "Labt.", "Matlh."}
// weekdayNamesSindhi list the weekday name abbreviations in the Sindhi.
weekdayNamesSindhi = []string{
"\u0633\u0648\u0645\u0631",
"\u0627\u06B1\u0627\u0631\u0648",
"\u0627\u0631\u0628\u0639",
"\u062E\u0645\u064A\u0633",
"\u062C\u0645\u0639\u0648",
"\u0687\u0646\u0687\u0631",
"\u0622\u0686\u0631",
}
// weekdayNamesSindhiAbbr list the weekday name abbreviations in the Sindhi.
weekdayNamesSindhiAbbr = []string{
"\u0633\u0648",
"\u0627\u06B1",
"\u0627\u0631",
"\u062E\u0645",
"\u062C\u0645\u0639\u0648",
"\u0687\u0646",
"\u0622\u0686",
}
// weekdayNamesSlovak list the weekday name abbreviations in the Slovak.
weekdayNamesSlovak = []string{"nedeľa", "pondelok", "utorok", "streda", "štvrtok", "piatok", "sobota"}
// weekdayNamesSlovakAbbr list the weekday name abbreviations in the Slovak.
weekdayNamesSlovakAbbr = []string{"ne", "po", "ut", "st", "št", "pi", "so"}
// weekdayNamesSlovenian list the weekday name abbreviations in the
// Slovenian.
weekdayNamesSlovenian = []string{"nedelja", "ponedeljek", "torek", "sreda", "četrtek", "petek", "sobota"}
// weekdayNamesSlovenianAbbr list the weekday name abbreviations in the
// Slovenian.
weekdayNamesSlovenianAbbr = []string{"ned.", "pon.", "tor.", "sre.", "čet.", "pet.", "sob."}
// weekdayNamesSomali list the weekday name abbreviations in the Somali.
weekdayNamesSomali = []string{"Axad", "Isniin", "Talaado", "Arbaco", "Khamiis", "Jimco", "Sabti"}
// weekdayNamesSomaliAbbr list the weekday name abbreviations in the Somali.
weekdayNamesSomaliAbbr = []string{"Axd", "Isn", "Tldo", "Arbc", "Khms", "Jmc", "Sbti"}
// weekdayNamesSotho list the weekday name abbreviations in the Sotho.
weekdayNamesSotho = []string{"Sontaha", "Mmantaha", "Labobedi", "Laboraru", "Labone", "Labohlane", "Moqebelo"}
// weekdayNamesSothoAbbr list the weekday name abbreviations in the Sotho.
weekdayNamesSothoAbbr = []string{"Son", "Mma", "Bed", "Rar", "Ne", "Hla", "Moq"}
// weekdayNamesSpanish list the weekday name abbreviations in the Spanish.
weekdayNamesSpanish = []string{"domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"}
// weekdayNamesSpanishAbbr list the weekday name abbreviations in the
// Spanish Argentina.
weekdayNamesSpanishAbbr = []string{"do.", "lu.", "ma.", "mi.", "ju.", "vi.", "sá."}
// weekdayNamesSpanishARAbbr list the weekday name abbreviations in the
// Spanish Argentina.
weekdayNamesSpanishARAbbr = []string{"dom.", "lun.", "mar.", "mié.", "jue.", "vie.", "sáb."}
// weekdayNamesSpanishUSAbbr list the weekday name abbreviations in the
// Spanish United States.
weekdayNamesSpanishUSAbbr = []string{"dom", "lun", "mar", "mié", "jue", "vie", "sáb"}
// weekdayNamesSwedish list the weekday name abbreviations in the Swedish.
weekdayNamesSwedish = []string{"söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag"}
// weekdayNamesSwedishAbbr list the weekday name abbreviations in the
// Swedish Argentina.
weekdayNamesSwedishAbbr = []string{"sön", "mån", "tis", "ons", "tor", "fre", "lör"}
// weekdayNamesSyriac list the weekday name abbreviations in the Syriac.
weekdayNamesSyriac = []string{
"\u071A\u0715 \u0712\u072B\u0712\u0710",
"\u072C\u072A\u071D\u0722 \u0712\u072B\u0712\u0710",
"\u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710",
"\u0710\u072A\u0712\u0725\u0710 \u0712\u072B\u0712\u0710",
"\u071A\u0721\u072B\u0710 \u0712\u072B\u0712\u0710",
"\u0725\u072A\u0718\u0712\u072C\u0710",
"\u072B\u0712\u072C\u0710",
}
// weekdayNamesSyriacAbbr list the weekday name abbreviations in the Syriac.
weekdayNamesSyriacAbbr = []string{
"\u070F\u0710 \u070F\u0712\u072B",
"\u070F\u0712 \u070F\u0712\u072B",
"\u070F\u0713 \u070F\u0712\u072B",
"\u070F\u0715 \u070F\u0712\u072B",
"\u070F\u0717 \u070F\u0712\u072B",
"\u070F\u0725\u072A\u0718\u0712",
"\u070F\u072B\u0712",
}
// weekdayNamesTajik list the weekday name abbreviations in the Tajik.
weekdayNamesTajik = []string{
"\u042F\u043A\u0448\u0430\u043D\u0431\u0435",
"\u0434\u0443\u0448\u0430\u043D\u0431\u0435",
"\u0441\u0435\u0448\u0430\u043D\u0431\u0435",
"\u0447\u043E\u0440\u0448\u0430\u043D\u0431\u0435",
"\u043F\u0430\u043D\u04B7\u0448\u0430\u043D\u0431\u0435",
"\u04B7\u0443\u043C\u044A\u0430",
"\u0448\u0430\u043D\u0431\u0435",
}
// weekdayNamesTajikAbbr list the weekday name abbreviations in the Tajik.
weekdayNamesTajikAbbr = []string{
"\u043F\u043A\u0448",
"\u0434\u0448\u0431",
"\u0441\u0448\u0431",
"\u0447\u0448\u0431",
"\u043F\u0448\u0431",
"\u04B7\u0443\u043C",
"\u0448\u043D\u0431",
}
// weekdayNamesTamazight list the weekday name abbreviations in the
// Tamazight.
weekdayNamesTamazight = []string{"lh'ed", "letnayen", "ttlata", "larebâa", "lexmis", "ldjemâa", "ssebt"}
// weekdayNamesTamazightAbbr list the weekday name abbreviations in the
// Tamazight Argentina.
weekdayNamesTamazightAbbr = []string{"lh'd", "let", "ttl", "lar", "lex", "ldj", "sse"}
// weekdayNamesTamil list the weekday name abbreviations in the Tamil.
weekdayNamesTamil = []string{
"\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BCD\u0BB1\u0BC1\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
"\u0B9A\u0BA9\u0BBF\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8",
}
// weekdayNamesTamilAbbr list the weekday name abbreviations in the Tamil.
weekdayNamesTamilAbbr = []string{
"\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BC1",
"\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
"\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD",
"\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD",
"\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0BA9\u0BCD",
"\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF",
"\u0B9A\u0BA9\u0BBF",
}
// weekdayNamesTamilLK list the weekday name abbreviations in the Tamil Sri
// Lanka.
weekdayNamesTamilLK = []string{
"\u0B9E\u0BBE\u0BAF\u0BBF\u0BB1\u0BC1",
"\u0BA4\u0BBF\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
"\u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD",
"\u0BAA\u0BC1\u0BA4\u0BA9\u0BCD",
"\u0BB5\u0BBF\u0BAF\u0BBE\u0BB4\u0BA9\u0BCD",
"\u0BB5\u0BC6\u0BB3\u0BCD\u0BB3\u0BBF",
"\u0B9A\u0BA9\u0BBF",
}
// weekdayNamesTamilLKAbbr list the weekday name abbreviations in the Tamil
// Sri Lanka.
weekdayNamesTamilLKAbbr = []string{
"\u0B9E\u0BBE\u0BAF\u0BBF.",
"\u0BA4\u0BBF\u0B99\u0BCD.",
"\u0B9A\u0BC6\u0BB5\u0BCD.",
"\u0BAA\u0BC1\u0BA4.",
"\u0BB5\u0BBF\u0BAF\u0BBE.",
"\u0BB5\u0BC6\u0BB3\u0BCD.",
"\u0B9A\u0BA9\u0BBF",
}
// weekdayNamesTatar list the weekday name abbreviations in the Tatar.
weekdayNamesTatar = []string{
"\u044F\u043A\u0448\u04D9\u043C\u0431\u0435",
"\u0434\u04AF\u0448\u04D9\u043C\u0431\u0435",
"\u0441\u0438\u0448\u04D9\u043C\u0431\u0435",
"\u0447\u04D9\u0440\u0448\u04D9\u043C\u0431\u0435",
"\u043F\u04D9\u043D\u0497\u0435\u0448\u04D9\u043C\u0431\u0435",
"\u0497\u043E\u043C\u0433\u0430",
"\u0448\u0438\u043C\u0431\u04D9",
}
// weekdayNamesTatarAbbr list the weekday name abbreviations in the Tatar.
weekdayNamesTatarAbbr = []string{
"\u044F\u043A\u0448.",
"\u0434\u04AF\u0448.",
"\u0441\u0438\u0448.",
"\u0447\u04D9\u0440\u0448.",
"\u043F\u04D9\u043D\u0497.",
"\u0497\u043E\u043C.",
"\u0448\u0438\u043C.",
}
// weekdayNamesTelugu list the weekday name abbreviations in the Telugu.
weekdayNamesTelugu = []string{
"\u0C06\u0C26\u0C3F\u0C35\u0C3E\u0C30\u0C02",
"\u0C38\u0C4B\u0C2E\u0C35\u0C3E\u0C30\u0C02",
"\u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02",
"\u0C2C\u0C41\u0C27\u0C35\u0C3E\u0C30\u0C02",
"\u0C17\u0C41\u0C30\u0C41\u0C35\u0C3E\u0C30\u0C02",
"\u0C36\u0C41\u0C15\u0C4D\u0C30\u0C35\u0C3E\u0C30\u0C02",
"\u0C36\u0C28\u0C3F\u0C35\u0C3E\u0C30\u0C02",
}
// weekdayNamesTeluguAbbr list the weekday name abbreviations in the Telugu.
weekdayNamesTeluguAbbr = []string{
"\u0C06\u0C26\u0C3F",
"\u0C38\u0C4B\u0C2E",
"\u0C2E\u0C02\u0C17\u0C33",
"\u0C2C\u0C41\u0C27",
"\u0C17\u0C41\u0C30\u0C41",
"\u0C36\u0C41\u0C15\u0C4D\u0C30",
"\u0C36\u0C28\u0C3F",
}
// weekdayNamesThai list the weekday name abbreviations in the Thai.
weekdayNamesThai = []string{
"\u0E2D\u0E32\u0E17\u0E34\u0E15\u0E22\u0E4C",
"\u0E08\u0E31\u0E19\u0E17\u0E23\u0E4C",
"\u0E2D\u0E31\u0E07\u0E04\u0E32\u0E23",
"\u0E1E\u0E38\u0E18",
"\u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35",
"\u0E28\u0E38\u0E01\u0E23\u0E4C",
"\u0E40\u0E2A\u0E32\u0E23\u0E4C",
}
// weekdayNamesThaiAbbr list the weekday name abbreviations in the Thai.
weekdayNamesThaiAbbr = []string{
"\u0E2D\u0E32.",
"\u0E08.",
"\u0E2D.",
"\u0E1E.",
"\u0E1E\u0E24.",
"\u0E28.",
"\u0E2A.",
}
// weekdayNamesTibetan list the weekday name abbreviations in the Tibetan.
weekdayNamesTibetan = []string{
"\u0F42\u0F5F\u0F60\u0F0B\u0F49\u0F72\u0F0B\u0F58\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0D",
"\u0F42\u0F5F\u0F60\u0F0B\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D",
}
// weekdayNamesTibetanAbbr list the weekday name abbreviations in the
// Tibetan.
weekdayNamesTibetanAbbr = []string{
"\u0F49\u0F72\u0F0B\u0F58\u0F0D",
"\u0F5F\u0FB3\u0F0B\u0F56\u0F0D",
"\u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D",
"\u0F63\u0FB7\u0F42\u0F0B\u0F54\u0F0D",
"\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D",
"\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0D",
"\u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D",
}
// weekdayNamesTigrinya list the weekday name abbreviations in the Tigrinya.
weekdayNamesTigrinya = []string{
"\u1230\u1295\u1260\u1275",
"\u1230\u1291\u12ED",
"\u1220\u1209\u1235",
"\u1228\u1261\u12D5",
"\u1283\u1219\u1235",
"\u12D3\u122D\u1262",
"\u1240\u12F3\u121D",
}
// weekdayNamesTigrinyaAbbr list the weekday name abbreviations in the
// Tigrinya.
weekdayNamesTigrinyaAbbr = []string{
"\u1230\u1295",
"\u1230\u1291",
"\u1230\u1209",
"\u1228\u1261",
"\u1213\u1219",
"\u12D3\u122D",
"\u1240\u12F3",
}
// weekdayNamesTsonga list the weekday name abbreviations in the Tsonga.
weekdayNamesTsonga = []string{"Sonta", "Musumbhunuku", "Ravumbirhi", "Ravunharhu", "Ravumune", "Ravuntlhanu", "Mugqivela"}
// weekdayNamesTsongaAbbr list the weekday name abbreviations in the Tsonga.
weekdayNamesTsongaAbbr = []string{"Son", "Mus", "Bir", "Har", "Ne", "Tlh", "Mug"}
// weekdayNamesTurkish list the weekday name abbreviations in the Turkish.
weekdayNamesTurkish = []string{"Pazar", "Pazartesi", "Sal\u0131", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"}
// weekdayNamesTurkishAbbr list the weekday name abbreviations in the
// Turkish.
weekdayNamesTurkishAbbr = []string{"Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"}
// weekdayNamesTurkmen list the weekday name abbreviations in the Turkmen.
weekdayNamesTurkmen = []string{"Ýekşenbe", "Duşenbe", "Sişenbe", "Çarşenbe", "Penşenbe", "Anna", "Şenbe"}
// weekdayNamesTurkmenAbbr list the weekday name abbreviations in the
// Turkmen.
weekdayNamesTurkmenAbbr = []string{"Ýb", "Db", "Sb", "Çb", "Pb", "An", "Şb"}
// weekdayNamesUkrainian list the weekday name abbreviations in the
// Ukrainian.
weekdayNamesUkrainian = []string{
"\u043D\u0435\u0434\u0456\u043B\u044F",
"\u043F\u043E\u043D\u0435\u0434\u0456\u043B\u043E\u043A",
"\u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A",
"\u0441\u0435\u0440\u0435\u0434\u0430",
"\u0447\u0435\u0442\u0432\u0435\u0440",
"\u043F'\u044F\u0442\u043D\u0438\u0446\u044F",
"\u0441\u0443\u0431\u043E\u0442\u0430",
}
// weekdayNamesUkrainianAbbr list the weekday name abbreviations in the
// Ukrainian.
weekdayNamesUkrainianAbbr = []string{
"\u041D\u0434",
"\u041F\u043D",
"\u0412\u0442",
"\u0421\u0440",
"\u0427\u0442",
"\u041F\u0442",
"\u0421\u0431",
}
// weekdayNamesSorbian list the weekday name abbreviations in the Sorbian.
weekdayNamesSorbian = []string{"njedźela", "póndźela", "wutora", "srjeda", "štwórtk", "pjatk", "sobota"}
// weekdayNamesSorbianAbbr list the weekday name abbreviations in the
// Sorbian.
weekdayNamesSorbianAbbr = []string{"nje", "pón", "wut", "srj", "štw", "pja", "sob"}
// weekdayNamesUrdu list the weekday name abbreviations in the Urdu.
weekdayNamesUrdu = []string{
"\u0627\u062A\u0648\u0627\u0631",
"\u067E\u064A\u0631",
"\u0645\u0646\u06AF\u0644",
"\u0628\u062F\u06BE",
"\u062C\u0645\u0639\u0631\u0627\u062A",
"\u062C\u0645\u0639\u0647",
"\u0647\u0641\u062A\u0647",
}
// weekdayNamesUrduIN list the weekday name abbreviations in the Urdu India.
weekdayNamesUrduIN = []string{
"\u0627\u062A\u0648\u0627\u0631",
"\u067E\u06CC\u0631",
"\u0645\u0646\u06AF\u0644",
"\u0628\u062F\u06BE",
"\u062C\u0645\u0639\u0631\u0627\u062A",
"\u062C\u0645\u0639\u06C1",
"\u06C1\u0641\u062A\u06C1",
}
// weekdayNamesUyghur list the weekday name abbreviations in the Uyghur.
weekdayNamesUyghur = []string{
"\u064A\u06D5\u0643\u0634\u06D5\u0646\u0628\u06D5",
"\u062F\u06C8\u0634\u06D5\u0646\u0628\u06D5",
"\u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5",
"\u0686\u0627\u0631\u0634\u06D5\u0646\u0628\u06D5",
"\u067E\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5",
"\u062C\u06C8\u0645\u06D5",
"\u0634\u06D5\u0646\u0628\u06D5",
}
// weekdayNamesUyghurAbbr list the weekday name abbreviations in the Uyghur.
weekdayNamesUyghurAbbr = []string{
"\u064A\u06D5",
"\u062F\u06C8",
"\u0633\u06D5",
"\u0686\u0627",
"\u067E\u06D5",
"\u062C\u06C8",
"\u0634\u06D5",
}
// weekdayNamesUzbekCyrillic list the weekday name abbreviations in the
// Uzbek Cyrillic.
weekdayNamesUzbekCyrillic = []string{
"\u044F\u043A\u0448\u0430\u043D\u0431\u0430",
"\u0434\u0443\u0448\u0430\u043D\u0431\u0430",
"\u0441\u0435\u0448\u0430\u043D\u0431\u0430",
"\u0447\u043E\u0440\u0448\u0430\u043D\u0431\u0430",
"\u043F\u0430\u0439\u0448\u0430\u043D\u0431\u0430",
"\u0436\u0443\u043C\u0430",
"\u0448\u0430\u043D\u0431\u0430",
}
// weekdayNamesUzbekCyrillicAbbr list the weekday name abbreviations in the
// Uzbek Cyrillic.
weekdayNamesUzbekCyrillicAbbr = []string{
"\u044F\u043A\u0448",
"\u0434\u0443\u0448",
"\u0441\u0435\u0448",
"\u0447\u043E\u0440",
"\u043F\u0430\u0439",
"\u0436\u0443\u043C",
"\u0448\u0430\u043D",
}
// weekdayNamesUzbek list the weekday name abbreviations in the Uzbek.
weekdayNamesUzbek = []string{"yakshanba", "dushanba", "seshanba", "chorshanba", "payshanba", "juma", "shanba"}
// weekdayNamesUzbekAbbr list the weekday name abbreviations in the Uzbek.
weekdayNamesUzbekAbbr = []string{"Yak", "Dush", "Sesh", "Chor", "Pay", "Jum", "Shan"}
// weekdayNamesValencian list the weekday name abbreviations in the
// Valencian.
weekdayNamesValencian = []string{"diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"}
// weekdayNamesValencianAbbr list the weekday name abbreviations in the
// Valencian.
weekdayNamesValencianAbbr = []string{"dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds."}
// weekdayNamesVenda list the weekday name abbreviations in the Venda.
weekdayNamesVenda = []string{"Swondaha", "Musumbuluwo", "Ḽavhuvhili", "Ḽavhuraru", "Ḽavhuṋa", "Ḽavhuṱanu", "Mugivhela"}
// weekdayNamesVendaAbbr list the weekday name abbreviations in the Venda.
weekdayNamesVendaAbbr = []string{"Swo", "Mus", "Vhi", "Rar", "Ṋa", "Ṱan", "Mug"}
// weekdayNamesVietnamese list the weekday name abbreviations in the
// Vietnamese.
weekdayNamesVietnamese = []string{"Chu\u0309 Nhâ\u0323t", "Th\u01B0\u0301 Hai", "Th\u01B0\u0301 Ba", "Th\u01B0\u0301 T\u01B0", "Th\u01B0\u0301 N\u0103m", "Th\u01B0\u0301 Sa\u0301u", "Th\u01B0\u0301 Ba\u0309y"}
// weekdayNamesVietnameseAbbr list the weekday name abbreviations in the
// Vietnamese.
weekdayNamesVietnameseAbbr = []string{"CN", "T2", "T3", "T4", "T5", "T6", "T7"}
// weekdayNamesWelsh list the weekday name abbreviations in the Welsh.
weekdayNamesWelsh = []string{"Dydd Sul", "Dydd Llun", "Dydd Mawrth", "Dydd Mercher", "Dydd Iau", "Dydd Gwener", "Dydd Sadwrn"}
// weekdayNamesWelshAbbr list the weekday name abbreviations in the Welsh.
weekdayNamesWelshAbbr = []string{"Sul", "Llun", "Maw", "Mer", "Iau", "Gwe", "Sad"}
// weekdayNamesWolof list the weekday name abbreviations in the Wolof.
weekdayNamesWolof = []string{"Dibéer", "Altine", "Talaata", "Àllarba", "Alxames", "Àjjuma", "Gaawu"}
// weekdayNamesWolofAbbr list the weekday name abbreviations in the Wolof.
weekdayNamesWolofAbbr = []string{"Dib.", "Alt.", "Tal.", "Àll.", "Alx.", "Àjj.", "Gaa."}
// weekdayNamesXhosa list the weekday name abbreviations in the Xhosa.
weekdayNamesXhosa = []string{"Cawe", "Mvulo", "Lwesibini", "Lwesithathu", "Lwesine", "Lwesihlanu", "Mgqibelo"}
// weekdayNamesXhosaAbbr list the weekday name abbreviations in the Xhosa.
weekdayNamesXhosaAbbr = []string{"iCa.", "uMv.", "uLwesib.", "uLwesith.", "uLwesin.", "uLwesihl.", "uMgq."}
// weekdayNamesYi list the weekday name abbreviations in the Yi.
weekdayNamesYi = []string{
"\uA46D\uA18F\uA44D",
"\uA18F\uA282\uA494",
"\uA18F\uA282\uA44D",
"\uA18F\uA282\uA315",
"\uA18F\uA282\uA1D6",
"\uA18F\uA282\uA26C",
"\uA18F\uA282\uA0D8",
}
// weekdayNamesYiAbbr list the weekday name abbreviations in the Yi.
weekdayNamesYiAbbr = []string{
"\uA46D\uA18F",
"\uA18F\uA494",
"\uA18F\uA44D",
"\uA18F\uA315",
"\uA18F\uA1D6",
"\uA18F\uA26C",
"\uA18F\uA0D8",
}
// weekdayNamesYiddish list the weekday name abbreviations in the Yiddish.
weekdayNamesYiddish = []string{
"\u05D6\u05D5\u05E0\u05D8\u05D9\u05E7",
"\u05DE\u05D0\u05B8\u05E0\u05D8\u05D9\u05E7",
"\u05D3\u05D9\u05E0\u05E1\u05D8\u05D9\u05E7",
"\u05DE\u05D9\u05D8\u05D5\u05D5\u05D0\u05DA",
"\u05D3\u05D0\u05E0\u05E2\u05E8\u05E9\u05D8\u05D9\u05E7",
"\u05E4\u05BF\u05E8\u05F2\u05B7\u05D8\u05D9\u05E7",
"\u05E9\u05D1\u05EA",
}
// weekdayNamesYiddishAbbr list the weekday name abbreviations in the
// Yiddish.
weekdayNamesYiddishAbbr = []string{
"\u05D9\u05D5\u05DD \u05D0",
"\u05D9\u05D5\u05DD \u05D1",
"\u05D9\u05D5\u05DD \u05D2",
"\u05D9\u05D5\u05DD \u05D3",
"\u05D9\u05D5\u05DD \u05D4",
"\u05D9\u05D5\u05DD \u05D5",
"\u05E9\u05D1\u05EA",
}
// weekdayNamesYoruba list the weekday name abbreviations in the Yoruba.
weekdayNamesYoruba = []string{
"\u1ECCj\u1ECD\u0301 Àìkú",
"\u1ECCj\u1ECD\u0301 Ajé",
"\u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun",
"\u1ECCj\u1ECD\u0301rú",
"\u1ECCj\u1ECD\u0301b\u1ECD",
"\u1ECCj\u1ECD\u0301 \u1EB8tì",
"\u1ECCj\u1ECD\u0301 Àbám\u1EB9\u0301ta",
}
// weekdayNamesYorubaAbbr list the weekday name abbreviations in the Yoruba.
weekdayNamesYorubaAbbr = []string{"Àìk", "Aj", "Ì\u1E63g", "\u1ECCjr", "\u1ECCjb", "\u1EB8t", "Àbá"}
// weekdayNamesZulu list the weekday name abbreviations in the Zulu.
weekdayNamesZulu = []string{"ISonto", "UMsombuluko", "ULwesibili", "ULwesithathu", "ULwesine", "ULwesihlanu", "UMgqibelo"}
// weekdayNamesZuluAbbr list the weekday name abbreviations in the Zulu.
weekdayNamesZuluAbbr = []string{"Son.", "Mso.", "Bi.", "Tha.", "Ne.", "Hla.", "Mgq."}
// apFmtAfrikaans defined the AM/PM name in the Afrikaans.
apFmtAfrikaans = "vm./nm."
// apFmtAlbanian defined the AM/PM name in the Albanian.
apFmtAlbanian = "p.d./m.d."
// apFmtAlsatian defined the AM/PM name in the Alsatian.
apFmtAlsatian = "vorm./nam."
// apFmtAmharic defined the AM/PM name in the Amharic.
apFmtAmharic = "\u1325\u12CB\u1275/\u12A8\u1230\u12D3\u1275"
// apFmtArabic defined the AM/PM name in the Arabic.
apFmtArabic = "\u0635/\u0645"
// apFmtAssamese defined the AM/PM name in the Assamese.
apFmtAssamese = "\u09F0\u09BE\u09A4\u09BF\u09AA\u09C1/\u0986\u09AC\u09C7\u09B2\u09BF"
// apFmtBreton defined the AM/PM name in the Assamese.
apFmtBreton = "A.M./G.M."
// apFmtBurmese defined the AM/PM name in the Assamese.
apFmtBurmese = "\u1014\u1036\u1014\u1000\u103A/\u100A\u1014\u1031"
// apFmtCameroon defined the AM/PM name in the Cameroon.
apFmtCameroon = "mat./soir"
// apFmtCentralKurdish defined the AM/PM name in the Central Kurdish.
apFmtCentralKurdish = "\u067E.\u0646/\u062F.\u0646"
// apFmtCuba defined the AM/PM name in the Cuba.
apFmtCuba = "a.m./p.m."
// apFmtCzech defined the AM/PM name in the Czech.
apFmtCzech = "dop./odp."
// apFmtDari defined the AM/PM name in the Dari.
apFmtDari = "\u0642.\u0638./\u0628.\u0638."
// apFmtDivehi defined the AM/PM name in the Divehi.
apFmtDivehi = "\u0789\u0786/\u0789\u078A"
// apFmtDzongkha defined the AM/PM name in the Dzongkha.
apFmtDzongkha = "\u0F66\u0F94\u0F0B\u0F46\u0F0B/\u0F55\u0FB1\u0F72\u0F0B\u0F46\u0F0B"
// apFmtFaroese defined the AM/PM name in the Faroese.
apFmtFaroese = "um fyr./um sein."
// apFmtFinnish defined the AM/PM name in the Finnish.
apFmtFinnish = "ap./ip."
// apFmtGreek defined the AM/PM name in the Greek.
apFmtGreek = "\u03C0\u03BC/\u03BC\u03BC"
// apFmtGujarati defined the AM/PM name in the Gujarati.
apFmtGujarati = "\u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8/\u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"
// apFmtHindi defined the AM/PM name in the Hindi.
apFmtHindi = "\u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928/\u0905\u092A\u0930\u093E\u0939\u094D\u0928"
// apFmtHungarian defined the AM/PM name in the Hungarian.
apFmtHungarian = "de./du."
// apFmtIcelandic defined the AM/PM name in the Icelandic.
apFmtIcelandic = "f.h./e.h."
// apFmtIgbo defined the AM/PM name in the Igbo.
apFmtIgbo = "A.M./P.M."
// apFmtIrish defined the AM/PM name in the Irish.
apFmtIrish = "r.n./i.n."
// apFmtJapanese defined the AM/PM name in the Japanese.
apFmtJapanese = "午前/午後"
// apFmtKannada defined the AM/PM name in the Kannada.
apFmtKannada = "\u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8/\u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8"
// apFmtKhmer defined the AM/PM name in the Khmer.
apFmtKhmer = "\u1796\u17D2\u179A\u17B9\u1780/\u179B\u17D2\u1784\u17B6\u1785"
// apFmtKonkani defined the AM/PM name in the Konkani.
apFmtKonkani = "\u092E.\u092A\u0942./\u092E.\u0928\u0902."
// apFmtKorean defined the AM/PM name in the Korean.
apFmtKorean = "오전/오후"
// apFmtKyrgyz defined the AM/PM name in the Kyrgyz.
apFmtKyrgyz = "\u0442\u04A3/\u0442\u043A"
// apFmtLao defined the AM/PM name in the Lao.
apFmtLao = "\u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87/\u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87"
// apFmtLatvian defined the AM/PM name in the Latvian.
apFmtLatvian = "priekšp./pēcp."
// apFmtLithuanian defined the AM/PM name in the Lithuanian.
apFmtLithuanian = "priešpiet/popiet"
// apFmtMacedonian defined the AM/PM name in the Macedonian.
apFmtMacedonian = "\u043F\u0440\u0435\u0442\u043F\u043B./\u043F\u043E\u043F\u043B."
// apFmtMalay defined the AM/PM name in the Malay.
apFmtMalay = "PG/PTG"
// apFmtMongolian defined the AM/PM name in the Mongolian.
apFmtMongolian = "\u04AF.\u04E9./\u04AF.\u0445."
// apFmtNigeria defined the AM/PM name in the Nigeria.
apFmtNigeria = "subaka/kikiiɗe"
// apFmtNorwegian defined the AM/PM name in the Norwegian.
apFmtNorwegian = "f.m./e.m."
// apFmtOromo defined the AM/PM name in the Oromo.
apFmtOromo = "WD/WB"
// apFmtPashto defined the AM/PM name in the Pashto.
apFmtPashto = "\u063A.\u0645./\u063A.\u0648."
// apFmtPersian defined the AM/PM name in the Persian.
apFmtPersian = "\u0642.\u0638/\u0628.\u0638"
// apFmtPunjabi defined the AM/PM name in the Punjabi.
apFmtPunjabi = "\u0A38\u0A35\u0A47\u0A30/\u0A38\u0A3C\u0A3E\u0A2E"
// apFmtSakha defined the AM/PM name in the Sakha.
apFmtSakha = "\u041A\u0418/\u041A\u041A"
// apFmtSamiNorthern defined the AM/PM name in the Sami (Northern).
apFmtSamiNorthern = "i.b./e.b."
// apFmtSanskrit defined the AM/PM name in the Sanskrit.
apFmtSanskrit = "\u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935/\u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924"
// apFmtScottishGaelic defined the AM/PM name in the Scottish Gaelic.
apFmtScottishGaelic = "m/f"
// apFmtSerbianLatin defined the AM/PM name in the Serbian (Latin).
apFmtSerbianLatin = "pre podne/po podne"
// apFmtSerbianLatinBA defined the AM/PM name in the Serbian (Latin) Bosnia
// and Herzegovina.
apFmtSerbianLatinBA = "prije podne/po podne"
// apFmtSinhala defined the AM/PM name in the Sinhala.
apFmtSinhala = "\u0DB4\u0DD9.\u0DC0./\u0DB4.\u0DC0."
// apFmtSlovenian defined the AM/PM name in the Slovenian.
apFmtSlovenian = "dop./pop."
// apFmtSomali defined the AM/PM name in the Somali.
apFmtSomali = "GH/GD"
// apFmtSpanish defined the AM/PM name in the Spanish.
apFmtSpanish = "a. m./p. m."
// apFmtSpanishAR defined the AM/PM name in the Spanish Argentina.
apFmtSpanishAR = "a. m./p. m."
// apFmtSwedish defined the AM/PM name in the Swedish.
apFmtSwedish = "fm/em"
// apFmtSyriac defined the AM/PM name in the Syriac.
apFmtSyriac = "\u0729.\u071B/\u0712.\u071B"
// apFmtTamil defined the AM/PM name in the Tamil.
apFmtTamil = "\u0B95\u0BBE\u0BB2\u0BC8/\u0BAE\u0BBE\u0BB2\u0BC8"
// apFmtTibetan defined the AM/PM name in the Tibetan.
apFmtTibetan = "\u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B/\u0F55\u0FB1\u0F72\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"
// apFmtTigrinya defined the AM/PM name in the Tigrinya.
apFmtTigrinya = "\u1295\u1309\u1206/\u12F5\u1215\u122A \u1250\u1275\u122A"
// apFmtTigrinyaER defined the AM/PM name in the Tigrinya Eritrea.
apFmtTigrinyaER = "\u1295\u1309\u1206 \u1230\u12D3\u1270/\u12F5\u1215\u122D \u1230\u12D3\u1275"
// apFmtTurkish defined the AM/PM name in the Turkish.
apFmtTurkish = "\u00F6\u00F6/\u00F6\u0053"
// apFmtUpperSorbian defined the AM/PM name in the Upper Sorbian.
apFmtUpperSorbian = "dopołdnja/popołdnju"
// apFmtUrdu defined the AM/PM name in the Urdu.
apFmtUrdu = "\u062F\u0646/\u0631\u0627\u062A"
// apFmtUyghur defined the AM/PM name in the Uyghur.
apFmtUyghur = "\u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646/\u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646"
// apFmtUzbek defined the AM/PM name in the Uzbek.
apFmtUzbek = "TO/TK"
// apFmtUzbekCyrillic defined the AM/PM name in the Uzbek Cyrillic.
apFmtUzbekCyrillic = "\u0422\u041E/\u0422\u041A"
// apFmtVietnamese defined the AM/PM name in the Vietnamese.
apFmtVietnamese = "SA/CH"
// apFmtWelsh defined the AM/PM name in the Welsh.
apFmtWelsh = "yb/yh"
// apFmtWolof defined the AM/PM name in the Wolof.
apFmtWolof = "Sub/Ngo"
// apFmtYi defined the AM/PM name in the Yi.
apFmtYi = "\uA3B8\uA111/\uA06F\uA2D2"
// apFmtYiddish defined the AM/PM name in the Yiddish.
apFmtYiddish = "\u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2/\u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"
// apFmtYoruba defined the AM/PM name in the Yoruba.
apFmtYoruba = "Àár\u1ECD\u0300/\u1ECC\u0300sán"
// switchArgumentFunc defined the switch argument printer function.
switchArgumentFunc = map[string]func(s string) string{
"[DBNum1]": func(s string) string {
r := strings.NewReplacer(
"0", "\u25CB", "1", "\u4E00", "2", "\u4E8C", "3", "\u4E09", "4", "\u56DB",
"5", "\u4E94", "6", "\u516D", "7", "\u4E03", "8", "\u516B", "9", "\u4E5D",
)
return r.Replace(s)
},
"[DBNum2]": func(s string) string {
r := strings.NewReplacer(
"0", "\u96F6", "1", "\u58F9", "2", "\u8D30", "3", "\u53C1", "4", "\u8086",
"5", "\u4F0D", "6", "\u9646", "7", "\u67D2", "8", "\u634C", "9", "\u7396",
)
return r.Replace(s)
},
"[DBNum3]": func(s string) string {
r := strings.NewReplacer(
"0", "\uFF10", "1", "\uFF11", "2", "\uFF12", "3", "\uFF13", "4", "\uFF14",
"5", "\uFF15", "6", "\uFF16", "7", "\uFF17", "8", "\uFF18", "9", "\uFF19",
)
return r.Replace(s)
},
}
// langNumFmtFunc defines functions to apply language number format code.
langNumFmtFunc = map[CultureName]func(f *File, numFmtID int) string{
CultureNameEnUS: func(f *File, numFmtID int) string {
return f.langNumFmtFuncEnUS(numFmtID)
},
CultureNameJaJP: func(f *File, numFmtID int) string {
return f.langNumFmtFuncJaJP(numFmtID)
},
CultureNameKoKR: func(f *File, numFmtID int) string {
return f.langNumFmtFuncKoKR(numFmtID)
},
CultureNameZhCN: func(f *File, numFmtID int) string {
return f.langNumFmtFuncZhCN(numFmtID)
},
CultureNameZhTW: func(f *File, numFmtID int) string {
return f.langNumFmtFuncZhTW(numFmtID)
},
}
)
// getSupportedLanguageInfo returns language information by giving language
// code. This function does not support different calendar type of the language
// currently. For example: the hexadecimal language code 3010429 (fa-IR,301)
// will be convert to 0429 (fa-IR).
func getSupportedLanguageInfo(lang string) (languageInfo, bool) {
hex := lang
if len(hex) > 4 {
hex = hex[len(hex)-4:]
}
n := new(big.Int)
n.SetString(hex, 16)
if info, ok := supportedLanguageInfo[int(n.Int64())]; ok {
return info, ok
}
if info, ok := supportedLanguageCodeInfo[lang]; ok {
return info, ok
}
for _, info := range supportedLanguageInfo {
if inStrSlice(info.tags, lang, false) != -1 {
return info, true
}
}
return languageInfo{}, false
}
// applyBuiltInNumFmt provides a function to returns a value after formatted
// with built-in number format code, or specified sort date format code.
func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string {
if f.options != nil && f.options.ShortDatePattern != "" {
if numFmtID == 14 {
fmtCode = f.options.ShortDatePattern
}
if numFmtID == 22 {
fmtCode = fmt.Sprintf("%s hh:mm", f.options.ShortDatePattern)
}
}
return format(c.V, fmtCode, date1904, cellType, f.options)
}
// langNumFmtFuncEnUS returns number format code by given date and time pattern
// for country code en-us.
func (f *File) langNumFmtFuncEnUS(numFmtID int) string {
shortDatePattern, longTimePattern := "M/d/yy", "h:mm:ss"
if f.options.ShortDatePattern != "" {
shortDatePattern = f.options.ShortDatePattern
}
if f.options.LongTimePattern != "" {
longTimePattern = f.options.LongTimePattern
}
if 32 <= numFmtID && numFmtID <= 35 {
return longTimePattern
}
if (27 <= numFmtID && numFmtID <= 31) || (50 <= numFmtID && numFmtID <= 58) {
return shortDatePattern
}
return ""
}
// langNumFmtFuncJaJP returns number format code by given date and time pattern
// for country code ja-jp.
func (f *File) langNumFmtFuncJaJP(numFmtID int) string {
if numFmtID == 30 && f.options.ShortDatePattern != "" {
return f.options.ShortDatePattern
}
if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" {
return f.options.LongTimePattern
}
return langNumFmt["ja-jp"][numFmtID]
}
// langNumFmtFuncKoKR returns number format code by given date and time pattern
// for country code ko-kr.
func (f *File) langNumFmtFuncKoKR(numFmtID int) string {
if numFmtID == 30 && f.options.ShortDatePattern != "" {
return f.options.ShortDatePattern
}
if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" {
return f.options.LongTimePattern
}
return langNumFmt["ko-kr"][numFmtID]
}
// langNumFmtFuncZhCN returns number format code by given date and time pattern
// for country code zh-cn.
func (f *File) langNumFmtFuncZhCN(numFmtID int) string {
if numFmtID == 30 && f.options.ShortDatePattern != "" {
return f.options.ShortDatePattern
}
if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" {
return f.options.LongTimePattern
}
return langNumFmt["zh-cn"][numFmtID]
}
// langNumFmtFuncZhTW returns number format code by given date and time pattern
// for country code zh-tw.
func (f *File) langNumFmtFuncZhTW(numFmtID int) string {
if numFmtID == 30 && f.options.ShortDatePattern != "" {
return f.options.ShortDatePattern
}
if (32 <= numFmtID && numFmtID <= 33) && f.options.LongTimePattern != "" {
return f.options.LongTimePattern
}
return langNumFmt["zh-tw"][numFmtID]
}
// checkDateTimePattern check and validate date and time options field value.
func (f *File) checkDateTimePattern() error {
for _, pattern := range []string{f.options.LongDatePattern, f.options.LongTimePattern, f.options.ShortDatePattern} {
p := nfp.NumberFormatParser()
for _, section := range p.Parse(pattern) {
for _, token := range section.Items {
if inStrSlice(supportedTokenTypes, token.TType, false) == -1 || inStrSlice(supportedNumberTokenTypes, token.TType, false) != -1 {
return ErrUnsupportedNumberFormat
}
}
}
}
return nil
}
// extractNumFmtDecimal returns decimal places, if has a decimal point token and
// zero place holder token from a number format code token list.
func extractNumFmtDecimal(tokens []nfp.Token) (int, bool, bool) {
decimal, point, zero := 0, false, false
for _, token := range tokens {
if token.TType == nfp.TokenTypeDecimalPoint {
point = true
}
if token.TType == nfp.TokenTypeZeroPlaceHolder {
if point {
decimal = len(token.TValue)
}
zero = true
}
}
return decimal, point, zero
}
// extractNumFmtDecimal returns decimal places from a number format code that
// has the same decimal places in positive part negative part or only positive
// part, if the given number format code is not suitable for numeric this
// function will return -1.
func (f *File) extractNumFmtDecimal(fmtCode string) int {
var (
p = nfp.NumberFormatParser()
pos, neg, posPoint, negPoint, posZero, negZero bool
posDecimal, negDecimal int
)
for i, section := range p.Parse(fmtCode) {
if i == 0 {
pos = true
posDecimal, posPoint, posZero = extractNumFmtDecimal(section.Items)
}
if i == 1 {
neg = true
negDecimal, negPoint, negZero = extractNumFmtDecimal(section.Items)
}
}
if !pos {
return -1
}
equalPosNegDecimal := posPoint && negPoint && posDecimal == negDecimal
equalPosNegZero := !posPoint && !negPoint && posZero && negZero
if neg {
if equalPosNegDecimal {
return posDecimal
}
if equalPosNegZero {
return 0
}
return -1
}
if posPoint {
return posDecimal
}
if posZero {
return 0
}
return -1
}
// getBuiltInNumFmtCode convert number format index to number format code with
// specified locale and language.
func (f *File) getBuiltInNumFmtCode(numFmtID int) (string, bool) {
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
return fmtCode, true
}
if isLangNumFmt(numFmtID) {
if fn, ok := langNumFmtFunc[f.options.CultureInfo]; ok {
return fn(f, numFmtID), true
}
}
return "", false
}
// prepareNumberic split the number into two before and after parts by a
// decimal point.
func (nf *numberFormat) prepareNumberic(value string) {
if nf.cellType != CellTypeNumber && nf.cellType != CellTypeDate {
return
}
if nf.isNumeric, _, _ = isNumeric(value); !nf.isNumeric {
return
}
}
// format provides a function to return a string parse by number format
// expression. If the given number format is not supported, this will return
// the original cell value.
func format(value, numFmt string, date1904 bool, cellType CellType, opts *Options) string {
p := nfp.NumberFormatParser()
nf := numberFormat{opts: opts, section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
nf.number, nf.valueSectionType = nf.getValueSectionType(value)
nf.prepareNumberic(value)
for i, section := range nf.section {
nf.sectionIdx = i
if section.Type != nf.valueSectionType {
continue
}
if nf.isNumeric {
switch section.Type {
case nfp.TokenSectionPositive:
return nf.alignmentHandler(nf.positiveHandler())
default:
return nf.alignmentHandler(nf.negativeHandler())
}
}
return nf.alignmentHandler(nf.textHandler())
}
return value
}
// getNumberPartLen returns the length of integer and fraction parts for the
// numeric.
func (nf *numberFormat) getNumberPartLen() (int, int) {
var intPart, fracPart, intLen, fracLen int
parts := strings.Split(strconv.FormatFloat(math.Abs(nf.number), 'f', -1, 64), ".")
intPart = len(parts[0])
if len(parts) == 2 {
fracPart = len(parts[1])
}
if nf.intHolder > intPart {
nf.intHolder = intPart
}
if intLen = intPart; nf.intPadding+nf.intHolder > intPart {
intLen = nf.intPadding + nf.intHolder
}
if fracLen = fracPart; fracPart > nf.fracHolder+nf.fracPadding {
fracLen = nf.fracHolder + nf.fracPadding
}
if nf.fracPadding > fracPart {
fracLen = nf.fracPadding
}
return intLen, fracLen
}
// getNumberFmtConf generate the number format padding and placeholder
// configurations.
func (nf *numberFormat) getNumberFmtConf() {
for _, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeHashPlaceHolder {
if nf.usePointer {
nf.fracHolder += len(token.TValue)
continue
}
nf.intHolder += len(token.TValue)
}
if token.TType == nfp.TokenTypeExponential {
nf.useScientificNotation = true
}
if token.TType == nfp.TokenTypeThousandsSeparator {
nf.useCommaSep = true
}
if token.TType == nfp.TokenTypePercent {
nf.percent += len(token.TValue)
}
if token.TType == nfp.TokenTypeDecimalPoint {
nf.usePointer = true
}
if token.TType == nfp.TokenTypeFraction {
nf.useFraction = true
}
if token.TType == nfp.TokenTypeSwitchArgument {
nf.switchArgument = token.TValue
}
if token.TType == nfp.TokenTypeZeroPlaceHolder {
nf.intHolder = 0
if nf.usePointer {
if nf.useScientificNotation {
nf.expBaseLen += len(token.TValue)
continue
}
nf.fracPadding += len(token.TValue)
continue
}
nf.intPadding += len(token.TValue)
}
}
}
// handleDigitsLiteral apply digit placeholder tokens for the number literal.
func handleDigitsLiteral(text string, tokenValueLen, intPartLen, hashZeroPartLen int, fill bool) (int, string) {
var result string
l := tokenValueLen
if intPartLen == 0 && len(text) > hashZeroPartLen {
l = len(text) + tokenValueLen - hashZeroPartLen
}
if len(text) < hashZeroPartLen {
intPartLen += len(text) - hashZeroPartLen
}
for i := 0; i < l; i++ {
j := i + intPartLen
if 0 <= j && j < len([]rune(text)) {
result += string([]rune(text)[j])
} else if fill {
result += " "
}
}
return l, result
}
// printNumberLiteral apply literal tokens for the pre-formatted text.
func (nf *numberFormat) printNumberLiteral(text string) string {
var (
result string
frac float64
useFraction bool
intPartLen, hashZeroPartLen int
)
if nf.usePositive {
result += "-"
}
lastNonFractionPartDigital := math.MaxInt
appearedFraction := false
numeratorPlaceHolder := 0
for idx := len(nf.section[nf.sectionIdx].Items) - 1; idx >= 0; idx-- {
token := nf.section[nf.sectionIdx].Items[idx]
if token.TType == nfp.TokenTypeFraction {
appearedFraction = true
} else if appearedFraction && (token.TType == nfp.TokenTypeHashPlaceHolder || token.TType == nfp.TokenTypeDigitalPlaceHolder) {
lastNonFractionPartDigital = idx - 1 // current idx belongs to the fraction part (numerator)
break
}
}
for idx, token := range nf.section[nf.sectionIdx].Items {
switch token.TType {
case nfp.TokenTypeHashPlaceHolder, nfp.TokenTypeZeroPlaceHolder:
hashZeroPartLen += len(token.TValue)
case nfp.TokenTypeDigitalPlaceHolder:
if lastNonFractionPartDigital >= 0 && idx < lastNonFractionPartDigital {
hashZeroPartLen += len(token.TValue)
}
}
}
for idx, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeCurrencyLanguage {
_, _ = nf.currencyLanguageHandler(token)
result += nf.currencyString
}
if token.TType == nfp.TokenTypeLiteral {
result += token.TValue
}
if token.TType == nfp.TokenTypeDigitalPlaceHolder && idx > lastNonFractionPartDigital {
// If it is a fraction part, it will be filled in by the fractalHandler()
// If not, fill placeHolder in directly here
if !useFraction { // numerator part
numeratorPlaceHolder = len(token.TValue)
}
} else if token.TType == nfp.TokenTypeHashPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder || token.TType == nfp.TokenTypeDigitalPlaceHolder {
digits, str := handleDigitsLiteral(text, len(token.TValue), intPartLen, hashZeroPartLen, token.TType == nfp.TokenTypeDigitalPlaceHolder)
intPartLen += digits
result += str
}
if token.TType == nfp.TokenTypeFraction {
_, frac = math.Modf(nf.number)
frac, useFraction = math.Abs(frac), true
}
if useFraction {
result += nf.fractionHandler(frac, token, numeratorPlaceHolder)
}
}
return nf.printSwitchArgument(result)
}
// fractionHandler handling fraction number format expression for positive and
// negative numeric.
func (nf *numberFormat) fractionHandler(frac float64, token nfp.Token, numeratorPlaceHolder int) string {
var rat, result string
if token.TType == nfp.TokenTypeDigitalPlaceHolder {
rat = floatToFraction(frac, numeratorPlaceHolder, len(token.TValue))
result += rat
}
if token.TType == nfp.TokenTypeDenominator {
denom, _ := strconv.ParseFloat(token.TValue, 64)
result += fmt.Sprintf("%d/%d", int(math.Round(frac*denom)), int(math.Round(denom)))
}
return result
}
// printCommaSep format number with thousands separator.
func printCommaSep(text string) string {
var (
target strings.Builder
subStr = strings.Split(text, ".")
length = len(subStr[0])
)
for i := 0; i < length; i++ {
if i > 0 && (length-i)%3 == 0 {
target.WriteString(",")
}
target.WriteByte(text[i])
}
if len(subStr) == 2 {
target.WriteString(".")
target.WriteString(subStr[1])
}
return target.String()
}
// printSwitchArgument format number with switch argument.
func (nf *numberFormat) printSwitchArgument(text string) string {
if nf.switchArgument == "" {
return text
}
if fn, ok := switchArgumentFunc[nf.switchArgument]; ok {
return fn(text)
}
return nf.value
}
// printBigNumber format number which precision great than 15 with fraction
// zero padding and percentage symbol.
func (nf *numberFormat) printBigNumber(decimal float64, fracLen int) string {
var exp float64
if nf.percent > 0 {
exp = 1
}
result := strings.TrimLeft(strconv.FormatFloat(decimal*math.Pow(100, exp), 'f', -1, 64), "-")
if nf.useCommaSep {
result = printCommaSep(result)
}
if fracLen > 0 {
if parts := strings.Split(result, "."); len(parts) == 2 {
fracPartLen := len(parts[1])
if fracPartLen < fracLen {
result = fmt.Sprintf("%s%s", result, strings.Repeat("0", fracLen-fracPartLen))
}
if fracPartLen > fracLen {
result = fmt.Sprintf("%s.%s", parts[0], parts[1][:fracLen])
}
} else {
result = fmt.Sprintf("%s.%s", result, strings.Repeat("0", fracLen))
}
}
if nf.percent > 0 {
return fmt.Sprintf("%s%%", result)
}
return result
}
// numberHandler handling number format expression for positive and negative
// numeric.
func (nf *numberFormat) numberHandler() string {
nf.getNumberFmtConf()
var (
num = nf.number
intLen, fracLen = nf.getNumberPartLen()
result string
)
if intLen+fracLen > 15 && !nf.useScientificNotation {
isNum, precision, decimal := isNumeric(nf.value)
if isNum && precision > 15 {
return nf.printNumberLiteral(nf.printBigNumber(decimal, fracLen))
}
}
paddingLen := intLen + fracLen
if fracLen > 0 {
paddingLen++
}
fmtCode := fmt.Sprintf("%%0%d.%df%s", paddingLen, fracLen, strings.Repeat("%%", nf.percent))
if nf.useScientificNotation {
if nf.expBaseLen != 2 {
return nf.value
}
fmtCode = fmt.Sprintf("%%.%dE%s", fracLen, strings.Repeat("%%", nf.percent))
}
if nf.percent > 0 {
num *= math.Pow(100, float64(nf.percent))
}
if nf.useFraction {
num = math.Floor(math.Abs(num))
}
if !nf.useScientificNotation {
ratio := math.Pow(10, float64(fracLen))
num = math.Round(num*ratio) / ratio
}
if result = fmt.Sprintf(fmtCode, math.Abs(num)); nf.useCommaSep {
result = printCommaSep(result)
}
return nf.printNumberLiteral(result)
}
// dateTimeHandler handling data and time number format expression for a
// positive numeric.
func (nf *numberFormat) dateTimeHandler() string {
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
if !nf.useMillisecond {
nf.t = nf.t.Add(time.Duration(math.Round(float64(nf.t.Nanosecond())/1e9)) * time.Second)
}
for i, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeCurrencyLanguage {
if changeNumFmtCode, err := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode {
return nf.value
}
nf.result += nf.currencyString
}
if token.TType == nfp.TokenTypeDateTimes {
nf.dateTimesHandler(i, token)
}
if token.TType == nfp.TokenTypeElapsedDateTimes {
nf.elapsedDateTimesHandler(token)
}
if token.TType == nfp.TokenTypeLiteral {
nf.result += token.TValue
continue
}
if token.TType == nfp.TokenTypeDecimalPoint {
nf.result += "."
}
if token.TType == nfp.TokenTypeSwitchArgument {
nf.switchArgument = token.TValue
}
if token.TType == nfp.TokenTypeZeroPlaceHolder {
zeroHolderLen := len(token.TValue)
if zeroHolderLen > 3 {
zeroHolderLen = 3
}
nf.result += fmt.Sprintf("%03d", nf.t.Nanosecond()/1e6)[:zeroHolderLen]
}
}
return nf.printSwitchArgument(nf.result)
}
// alignmentHandler will be handling alignment token for each number format
// selection for a number format expression.
func (nf *numberFormat) alignmentHandler(result string) string {
tokens := nf.section[nf.sectionIdx].Items
if len(tokens) == 0 {
return result
}
if tokens[0].TType == nfp.TokenTypeAlignment {
result = nfp.Whitespace + result
}
if l := len(tokens); tokens[l-1].TType == nfp.TokenTypeAlignment {
result += nfp.Whitespace
}
return result
}
// positiveHandler will be handling positive selection for a number format
// expression.
func (nf *numberFormat) positiveHandler() string {
var fmtNum bool
for _, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeGeneral {
if isNum, precision, _ := isNumeric(nf.value); isNum && precision > 11 {
return strconv.FormatFloat(nf.number, 'G', 10, 64)
}
return nf.value
}
if inStrSlice(supportedNumberTokenTypes, token.TType, true) != -1 {
fmtNum = true
}
if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 {
if fmtNum || nf.number < 0 {
return nf.value
}
var useDateTimeTokens bool
for _, token := range nf.section[nf.sectionIdx].Items {
if inStrSlice(supportedDateTimeTokenTypes, token.TType, false) != -1 {
if useDateTimeTokens && nf.useMillisecond {
return nf.value
}
useDateTimeTokens = true
}
if inStrSlice(supportedNumberTokenTypes, token.TType, false) != -1 {
if token.TType == nfp.TokenTypeZeroPlaceHolder {
nf.useMillisecond = true
continue
}
return nf.value
}
}
return nf.dateTimeHandler()
}
}
return nf.numberHandler()
}
// currencyLanguageHandler will be handling currency and language types tokens
// for a number format expression.
func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (bool, error) {
for _, part := range token.Parts {
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
return false, ErrUnsupportedNumberFormat
}
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
if inStrSlice([]string{"F800", "x-sysdate", "1010000"}, part.Token.TValue, false) != -1 {
if nf.opts != nil && nf.opts.LongDatePattern != "" {
nf.value = format(nf.value, nf.opts.LongDatePattern, nf.date1904, nf.cellType, nf.opts)
return true, nil
}
part.Token.TValue = "409"
}
if inStrSlice([]string{"F400", "x-systime"}, part.Token.TValue, false) != -1 {
if nf.opts != nil && nf.opts.LongTimePattern != "" {
nf.value = format(nf.value, nf.opts.LongTimePattern, nf.date1904, nf.cellType, nf.opts)
return true, nil
}
part.Token.TValue = "409"
}
if _, ok := getSupportedLanguageInfo(strings.ToUpper(part.Token.TValue)); !ok {
return false, ErrUnsupportedNumberFormat
}
nf.localCode = strings.ToUpper(part.Token.TValue)
}
if part.Token.TType == nfp.TokenSubTypeCurrencyString {
nf.currencyString = part.Token.TValue
return false, nil
}
}
return false, nil
}
// localAmPm return AM/PM name by supported language ID.
func (nf *numberFormat) localAmPm(ap string) string {
if languageInfo, ok := getSupportedLanguageInfo(nf.localCode); ok {
return languageInfo.apFmt
}
return ap
}
// localMonthsNameAfrikaans returns the Afrikaans name of the month.
func localMonthsNameAfrikaans(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAfrikaansAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAfrikaans[int(t.Month())-1]
}
return monthNamesAfrikaansAbbr[int(t.Month())-1][:1]
}
// localMonthsNameAlbanian returns the Albanian name of the month.
func localMonthsNameAlbanian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAlbanianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAlbanian[int(t.Month())-1]
}
return monthNamesAlbanianAbbr[int(t.Month())-1][:1]
}
// localMonthsNameAlsatian returns the Alsatian name of the month.
func localMonthsNameAlsatian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAlsatianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAlsatian[int(t.Month())-1]
}
return monthNamesAlsatianAbbr[int(t.Month())-1][:1]
}
// localMonthsNameAlsatianFrance returns the Alsatian France name of the month.
func localMonthsNameAlsatianFrance(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAlsatianFranceAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAlsatianFrance[int(t.Month())-1]
}
return monthNamesAlsatianFranceAbbr[int(t.Month())-1][:1]
}
// localMonthsNameAmharic returns the Amharic name of the month.
func localMonthsNameAmharic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAmharicAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAmharic[int(t.Month())-1]
}
return string([]rune(monthNamesAmharic[int(t.Month())-1])[:1])
}
// localMonthsNameArabic returns the Arabic name of the month.
func localMonthsNameArabic(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesArabic[int(t.Month())-1])[:1])
}
return monthNamesArabic[int(t.Month())-1]
}
// localMonthsNameArabicIraq returns the Arabic Iraq name of the month.
func localMonthsNameArabicIraq(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesArabicIraq[int(t.Month())-1])[:1])
}
return monthNamesArabicIraq[int(t.Month())-1]
}
// localMonthsNameArmenian returns the Armenian name of the month.
func localMonthsNameArmenian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesArmenianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesArmenian[int(t.Month())-1]
}
return string([]rune(monthNamesArmenianAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameAssamese returns the Assamese name of the month.
func localMonthsNameAssamese(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAssameseAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAssamese[int(t.Month())-1]
}
return string([]rune(monthNamesAssameseAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameAzerbaijaniCyrillic returns the Azerbaijani (Cyrillic) name of the month.
func localMonthsNameAzerbaijaniCyrillic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAzerbaijaniCyrillicAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAzerbaijaniCyrillic[int(t.Month())-1]
}
return string([]rune(monthNamesAzerbaijaniCyrillic[int(t.Month())-1])[:1])
}
// localMonthsNameAzerbaijani returns the Azerbaijani name of the month.
func localMonthsNameAzerbaijani(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAzerbaijaniAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAzerbaijani[int(t.Month())-1]
}
return string([]rune(monthNamesAzerbaijani[int(t.Month())-1])[:1])
}
// localMonthsNameAustria returns the Austria name of the month.
func localMonthsNameAustria(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesAustriaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesAustria[int(t.Month())-1]
}
return monthNamesAustriaAbbr[int(t.Month())-1][:1]
}
// localMonthsNameBangla returns the German name of the month.
func localMonthsNameBangla(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesBangla[int(t.Month())-1]
}
return string([]rune(monthNamesBangla[int(t.Month())-1])[:1])
}
// localMonthsNameBashkir returns the Bashkir name of the month.
func localMonthsNameBashkir(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBashkirAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBashkir[int(t.Month())-1]
}
return string([]rune(monthNamesBashkir[int(t.Month())-1])[:1])
}
// localMonthsNameBasque returns the Basque name of the month.
func localMonthsNameBasque(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBasqueAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBasque[int(t.Month())-1]
}
return string([]rune(monthNamesBasque[int(t.Month())-1])[:1])
}
// localMonthsNameBelarusian returns the Belarusian name of the month.
func localMonthsNameBelarusian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBelarusianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBelarusian[int(t.Month())-1]
}
return string([]rune(monthNamesBelarusian[int(t.Month())-1])[:1])
}
// localMonthsNameBosnianCyrillic returns the Bosnian (Cyrillic) name of the month.
func localMonthsNameBosnianCyrillic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBosnianCyrillicAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBosnianCyrillic[int(t.Month())-1]
}
return string([]rune(monthNamesBosnianCyrillic[int(t.Month())-1])[:1])
}
// localMonthsNameBosnian returns the Bosnian name of the month.
func localMonthsNameBosnian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBosnianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBosnian[int(t.Month())-1]
}
return string([]rune(monthNamesBosnian[int(t.Month())-1])[:1])
}
// localMonthsNameBreton returns the Breton name of the month.
func localMonthsNameBreton(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBretonAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBreton[int(t.Month())-1]
}
return string([]rune(monthNamesBreton[int(t.Month())-1])[:1])
}
// localMonthsNameBulgarian returns the Bulgarian name of the month.
func localMonthsNameBulgarian(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesBulgarian[int(t.Month())-1])[:3])
}
if abbr == 4 {
return monthNamesBulgarian[int(t.Month())-1]
}
return string([]rune(monthNamesBulgarian[int(t.Month())-1])[:1])
}
// localMonthsNameBurmese returns the Burmese name of the month.
func localMonthsNameBurmese(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesBurmeseAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesBurmese[int(t.Month())-1]
}
return string([]rune(monthNamesBurmese[int(t.Month())-1])[:1])
}
// localMonthsNameCaribbean returns the Caribbean name of the month.
func localMonthsNameCaribbean(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesCaribbeanAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesCaribbean[int(t.Month())-1]
}
return monthNamesCaribbeanAbbr[int(t.Month())-1][:1]
}
// localMonthsNameCentralKurdish returns the Central Kurdish name of the month.
func localMonthsNameCentralKurdish(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesCentralKurdish[int(t.Month())-1])[:1])
}
return monthNamesCentralKurdish[int(t.Month())-1]
}
// localMonthsNameCherokee returns the Cherokee name of the month.
func localMonthsNameCherokee(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesCherokeeAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesCherokee[int(t.Month())-1]
}
return string([]rune(monthNamesCherokee[int(t.Month())-1])[:1])
}
// localMonthsNameChinese1 returns the Chinese name of the month.
func localMonthsNameChinese1(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesChineseNum[t.Month()-1]
}
if abbr == 4 {
return monthNamesChinese[int(t.Month())-1]
}
return monthNamesChineseAbbr[int(t.Month())-1]
}
// localMonthsNameChinese2 returns the Chinese name of the month.
func localMonthsNameChinese2(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesChinese[int(t.Month())-1]
}
return monthNamesChineseAbbr[int(t.Month())-1]
}
// localMonthsNameChinese3 returns the Chinese name of the month.
func localMonthsNameChinese3(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesChineseNum[t.Month()-1]
}
return strconv.Itoa(int(t.Month()))
}
// localMonthsNameCorsican returns the Corsican name of the month.
func localMonthsNameCorsican(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesCorsicanAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesCorsican[int(t.Month())-1]
}
return string([]rune(monthNamesCorsican[int(t.Month())-1])[:1])
}
// localMonthsNameCroatian returns the Croatian name of the month.
func localMonthsNameCroatian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesCroatianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesCroatian[int(t.Month())-1]
}
return string([]rune(monthNamesCroatian[int(t.Month())-1])[:1])
}
// localMonthsNameCroatianLatin returns the Croatian (Latin) name of the month.
func localMonthsNameCroatianLatin(t time.Time, abbr int) string {
if abbr == 3 {
if t.Month() == 2 {
return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:4])
}
return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesCroatian[int(t.Month())-1]
}
return string([]rune(monthNamesCroatian[(t.Month() - 1)])[:1])
}
// localMonthsNameCzech returns the Czech name of the month.
func localMonthsNameCzech(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesCzechAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesCzech[int(t.Month())-1]
}
return string([]rune(monthNamesCzech[int(t.Month())-1])[:1])
}
// localMonthsNameDanish returns the Danish name of the month.
func localMonthsNameDanish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesDanishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesDanish[int(t.Month())-1]
}
return string([]rune(monthNamesDanish[int(t.Month())-1])[:1])
}
// localMonthsNameDari returns the Dari name of the month.
func localMonthsNameDari(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesDariAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesPersian[int(t.Month())-1]
}
return string([]rune(monthNamesPersian[int(t.Month())-1])[:1])
}
// localMonthsNameDariAfghanistan returns the Dari (Afghanistan) name of the
// month.
func localMonthsNameDariAfghanistan(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesDariAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesDari[int(t.Month())-1]
}
return string([]rune(monthNamesDari[int(t.Month())-1])[:1])
}
// localMonthsNameDivehi returns the Divehi name of the month.
func localMonthsNameDivehi(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesDivehi[int(t.Month())-1]
}
return string([]rune(monthNamesDivehi[int(t.Month())-1])[:1])
}
// localMonthsNameDutch returns the Dutch name of the month.
func localMonthsNameDutch(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesDutchAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesDutch[int(t.Month())-1]
}
return string([]rune(monthNamesDutch[int(t.Month())-1])[:1])
}
// localMonthsNameDzongkha returns the Dzongkha name of the month.
func localMonthsNameDzongkha(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesDzongkhaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesDzongkha[int(t.Month())-1]
}
return string([]rune(monthNamesDzongkha[int(t.Month())-1])[:1])
}
// localMonthsNameEnglish returns the English name of the month.
func localMonthsNameEnglish(t time.Time, abbr int) string {
if abbr == 3 {
return t.Month().String()[:3]
}
if abbr == 4 {
return t.Month().String()
}
return t.Month().String()[:1]
}
// localMonthsNameEstonian returns the Estonian name of the month.
func localMonthsNameEstonian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesEstonianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesEstonian[int(t.Month())-1]
}
return monthNamesEstonianAbbr[int(t.Month())-1][:1]
}
// localMonthsNameFaroese returns the Faroese name of the month.
func localMonthsNameFaroese(t time.Time, abbr int) string {
if abbr == 3 {
return monthsNameFaroeseAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesFaroese[int(t.Month())-1]
}
return monthsNameFaroeseAbbr[int(t.Month())-1][:1]
}
// localMonthsNameFilipino returns the Filipino name of the month.
func localMonthsNameFilipino(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesFilipinoAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesFilipino[int(t.Month())-1]
}
return fmt.Sprintf("%02d", int(t.Month()))
}
// localMonthsNameFinnish returns the Finnish name of the month.
func localMonthsNameFinnish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesFinnishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesFinnish[int(t.Month())-1]
}
return fmt.Sprintf("%02d", int(t.Month()))
}
// localMonthsNameFrench returns the French name of the month.
func localMonthsNameFrench(t time.Time, abbr int) string {
if abbr == 3 {
month := monthNamesFrench[int(t.Month())-1]
if len([]rune(month)) <= 4 {
return monthNamesFrench[int(t.Month())-1]
}
return monthNamesFrenchAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesFrench[int(t.Month())-1]
}
return monthNamesFrenchAbbr[int(t.Month())-1][:1]
}
// localMonthsNameFrisian returns the Frisian name of the month.
func localMonthsNameFrisian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesFrisianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesFrisian[int(t.Month())-1]
}
return monthNamesFrisian[int(t.Month())-1][:1]
}
// localMonthsNameFulah returns the Fulah name of the month.
func localMonthsNameFulah(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesFulahAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesFulah[int(t.Month())-1]
}
return monthNamesFulah[int(t.Month())-1][:1]
}
// localMonthsNameGalician returns the Galician name of the month.
func localMonthsNameGalician(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGalicianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesGalician[int(t.Month())-1]
}
return monthNamesGalician[int(t.Month())-1][:1]
}
// localMonthsNameGeorgian returns the Georgian name of the month.
func localMonthsNameGeorgian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGeorgianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesGeorgian[int(t.Month())-1]
}
return string([]rune(monthNamesGeorgian[int(t.Month())-1])[:1])
}
// localMonthsNameGerman returns the German name of the month.
func localMonthsNameGerman(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGermanAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesGerman[int(t.Month())-1]
}
return monthNamesGermanAbbr[int(t.Month())-1][:1]
}
// localMonthsNameGreek returns the Greek name of the month.
func localMonthsNameGreek(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGreekAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesGreek[int(t.Month())-1]
}
return string([]rune(monthNamesGreekAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameGreenlandic returns the Greenlandic name of the month.
func localMonthsNameGreenlandic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGreenlandicAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesGreenlandic[int(t.Month())-1]
}
return string([]rune(monthNamesGreenlandicAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameGuarani returns the Guarani name of the month.
func localMonthsNameGuarani(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGuaraniAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesGuarani[int(t.Month())-1]
}
return string([]rune(monthNamesGuaraniAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameGujarati returns the Gujarati name of the month.
func localMonthsNameGujarati(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesGujaratiAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesGujarati[int(t.Month())-1]
}
return string([]rune(monthNamesGujaratiAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameHausa returns the Hausa name of the month.
func localMonthsNameHausa(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesHausa[int(t.Month())-1])[:3])
}
if abbr == 4 {
return monthNamesHausa[int(t.Month())-1]
}
return string([]rune(monthNamesHausa[int(t.Month())-1])[:1])
}
// localMonthsNameHawaiian returns the Hawaiian name of the month.
func localMonthsNameHawaiian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesHawaiianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesHawaiian[int(t.Month())-1]
}
return string([]rune(monthNamesHawaiianAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameHebrew returns the Hebrew name of the month.
func localMonthsNameHebrew(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesHebrew[int(t.Month())-1])[:3])
}
if abbr == 4 {
return monthNamesHebrew[int(t.Month())-1]
}
return string([]rune(monthNamesHebrew[int(t.Month())-1])[:1])
}
// localMonthsNameHindi returns the Hindi name of the month.
func localMonthsNameHindi(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesHindi[int(t.Month())-1]
}
return string([]rune(monthNamesHindi[int(t.Month())-1])[:1])
}
// localMonthsNameHungarian returns the Hungarian name of the month.
func localMonthsNameHungarian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesHungarianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesHungarian[int(t.Month())-1]
}
return string([]rune(monthNamesHungarianAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameIcelandic returns the Icelandic name of the month.
func localMonthsNameIcelandic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesIcelandicAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesIcelandic[int(t.Month())-1]
}
return string([]rune(monthNamesIcelandicAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameIgbo returns the Igbo name of the month.
func localMonthsNameIgbo(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesIgboAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesIgbo[int(t.Month())-1]
}
return string([]rune(monthNamesIgboAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameIndonesian returns the Indonesian name of the month.
func localMonthsNameIndonesian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesIndonesianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesIndonesian[int(t.Month())-1]
}
return string([]rune(monthNamesIndonesianAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameInuktitut returns the Inuktitut name of the month.
func localMonthsNameInuktitut(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesInuktitutAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesInuktitut[int(t.Month())-1]
}
return string([]rune(monthNamesInuktitutAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameIrish returns the Irish name of the month.
func localMonthsNameIrish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesIrishAbbr[(t.Month() - 1)]
}
if abbr == 4 {
return monthNamesIrish[int(t.Month())-1]
}
return monthNamesIrishAbbr[int(t.Month())-1][:1]
}
// localMonthsNameItalian returns the Italian name of the month.
func localMonthsNameItalian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesItalianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesItalian[int(t.Month())-1]
}
return monthNamesItalianAbbr[int(t.Month())-1][:1]
}
// localMonthsNameKannada returns the Kannada name of the month.
func localMonthsNameKannada(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKannadaAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKannada[int(t.Month())-1]
}
return string([]rune(monthNamesKannada[int(t.Month())-1])[:1])
}
// localMonthsNameKashmiri returns the Kashmiri name of the month.
func localMonthsNameKashmiri(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesKashmiri[int(t.Month())-1])[:1])
}
return monthNamesKashmiri[int(t.Month())-1]
}
// localMonthsNameKazakh returns the Kazakh name of the month.
func localMonthsNameKazakh(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKazakhAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKazakh[int(t.Month())-1]
}
return string([]rune(monthNamesKazakh[int(t.Month())-1])[:1])
}
// localMonthsNameKhmer returns the Khmer name of the month.
func localMonthsNameKhmer(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKhmerAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKhmer[int(t.Month())-1]
}
return string([]rune(monthNamesKhmerAbbr[int(t.Month())+11])[:1])
}
// localMonthsNameKiche returns the Kiche name of the month.
func localMonthsNameKiche(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKicheAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKiche[int(t.Month())-1]
}
return string([]rune(monthNamesKicheAbbr[(t.Month() - 1)])[:1])
}
// localMonthsNameKinyarwanda returns the Kinyarwanda name of the month.
func localMonthsNameKinyarwanda(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKinyarwandaAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKinyarwanda[int(t.Month())-1]
}
return string([]rune(monthNamesKinyarwanda[(t.Month() - 1)])[:1])
}
// localMonthsNameKiswahili returns the Kiswahili name of the month.
func localMonthsNameKiswahili(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKiswahiliAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKiswahili[int(t.Month())-1]
}
return string([]rune(monthNamesKiswahili[(t.Month() - 1)])[:1])
}
// localMonthsNameKonkani returns the Konkani name of the month.
func localMonthsNameKonkani(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKonkaniAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKonkani[int(t.Month())-1]
}
return string([]rune(monthNamesKonkani[(t.Month() - 1)])[:1])
}
// localMonthsNameKorean returns the Korean name of the month.
func localMonthsNameKorean(t time.Time, abbr int) string {
if abbr == 4 || abbr > 6 {
return monthNamesKoreanAbbr[int(t.Month())-1]
}
return strconv.Itoa(int(t.Month()))
}
// localMonthsNameKyrgyz returns the Kyrgyz name of the month.
func localMonthsNameKyrgyz(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesKyrgyzAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesKyrgyz[int(t.Month())-1]
}
return string([]rune(monthNamesKyrgyz[(t.Month() - 1)])[:1])
}
// localMonthsNameLao returns the Lao name of the month.
func localMonthsNameLao(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLaoAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLao[int(t.Month())-1]
}
return string([]rune(monthNamesLao[(t.Month() - 1)])[:1])
}
// localMonthsNameLatin returns the Latin name of the month.
func localMonthsNameLatin(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLatinAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLatin[int(t.Month())-1]
}
return string([]rune(monthNamesLatin[(t.Month() - 1)])[:1])
}
// localMonthsNameLatvian returns the Latvian name of the month.
func localMonthsNameLatvian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLatvianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLatvian[int(t.Month())-1]
}
return string([]rune(monthNamesLatvian[(t.Month() - 1)])[:1])
}
// localMonthsNameLithuanian returns the Lithuanian name of the month.
func localMonthsNameLithuanian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLithuanianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLithuanian[int(t.Month())-1]
}
return string([]rune(monthNamesLithuanian[(t.Month() - 1)])[:1])
}
// localMonthsNameLowerSorbian returns the LowerSorbian name of the month.
func localMonthsNameLowerSorbian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLowerSorbianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLowerSorbian[int(t.Month())-1]
}
return string([]rune(monthNamesLowerSorbian[(t.Month() - 1)])[:1])
}
// localMonthsNameLuxembourgish returns the Luxembourgish name of the month.
func localMonthsNameLuxembourgish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesLuxembourgishAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesLuxembourgish[int(t.Month())-1]
}
return string([]rune(monthNamesLuxembourgish[(t.Month() - 1)])[:1])
}
// localMonthsNameMacedonian returns the Macedonian name of the month.
func localMonthsNameMacedonian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMacedonianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMacedonian[int(t.Month())-1]
}
return string([]rune(monthNamesMacedonian[(t.Month() - 1)])[:1])
}
// localMonthsNameMalay returns the Malay name of the month.
func localMonthsNameMalay(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMalayAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMalay[int(t.Month())-1]
}
return string([]rune(monthNamesMalay[(t.Month() - 1)])[:1])
}
// localMonthsNameMalayalam returns the Malayalam name of the month.
func localMonthsNameMalayalam(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMalayalamAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMalayalam[int(t.Month())-1]
}
return string([]rune(monthNamesMalayalam[(t.Month() - 1)])[:1])
}
// localMonthsNameMaltese returns the Maltese name of the month.
func localMonthsNameMaltese(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMalteseAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMaltese[int(t.Month())-1]
}
return string([]rune(monthNamesMaltese[(t.Month() - 1)])[:1])
}
// localMonthsNameMaori returns the Maori name of the month.
func localMonthsNameMaori(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMaoriAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMaori[int(t.Month())-1]
}
return string([]rune(monthNamesMaori[(t.Month() - 1)])[:1])
}
// localMonthsNameMapudungun returns the Mapudungun name of the month.
func localMonthsNameMapudungun(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesMapudungun[(t.Month() - 1)])[:1])
}
return monthNamesMapudungun[int(t.Month())-1]
}
// localMonthsNameMarathi returns the Marathi name of the month.
func localMonthsNameMarathi(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMarathiAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMarathi[int(t.Month())-1]
}
return string([]rune(monthNamesMarathi[(t.Month() - 1)])[:1])
}
// localMonthsNameMohawk returns the Mohawk name of the month.
func localMonthsNameMohawk(t time.Time, abbr int) string {
if abbr == 3 {
return t.Month().String()[:3]
}
if abbr == 4 || abbr > 6 {
return monthNamesMohawk[int(t.Month())-1]
}
return string([]rune(monthNamesMohawk[(t.Month() - 1)])[:1])
}
// localMonthsNameMongolian returns the Mongolian name of the month.
func localMonthsNameMongolian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMongolianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesMongolian[int(t.Month())-1]
}
return string([]rune(monthNamesMongolian[(t.Month() - 1)])[:1])
}
// localMonthsNameMorocco returns the Morocco name of the month.
func localMonthsNameMorocco(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesMoroccoAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesFrench[int(t.Month())-1]
}
return monthNamesFrench[int(t.Month())-1][:1]
}
// localMonthsNameNepali returns the Nepali name of the month.
func localMonthsNameNepali(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesNepaliAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesNepali[int(t.Month())-1]
}
return string([]rune(monthNamesNepali[(t.Month() - 1)])[:1])
}
// localMonthsNameNepaliIN returns the India Nepali name of the month.
func localMonthsNameNepaliIN(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesNepaliINAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesNepaliIN[int(t.Month())-1]
}
return string([]rune(monthNamesNepaliIN[(t.Month() - 1)])[:1])
}
// localMonthsNameNigeria returns the Nigeria name of the month.
func localMonthsNameNigeria(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesNigeriaAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesNigeria[int(t.Month())-1]
}
return monthNamesNigeria[int(t.Month())-1][:1]
}
// localMonthsNameNorwegian returns the Norwegian name of the month.
func localMonthsNameNorwegian(t time.Time, abbr int) string {
if abbr == 3 {
return monthsNameFaroeseAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesNorwegian[int(t.Month())-1]
}
return string([]rune(monthNamesNorwegian[(t.Month() - 1)])[:1])
}
// localMonthsNameOccitan returns the Occitan name of the month.
func localMonthsNameOccitan(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesOccitanAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesOccitan[int(t.Month())-1]
}
return string([]rune(monthNamesOccitan[(t.Month() - 1)])[:1])
}
// localMonthsNameOdia returns the Odia name of the month.
func localMonthsNameOdia(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesOdia[(t.Month() - 1)])[:1])
}
return monthNamesOdia[int(t.Month())-1]
}
// localMonthsNameOromo returns the Oromo name of the month.
func localMonthsNameOromo(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesOromoAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesOromo[int(t.Month())-1]
}
return string([]rune(monthNamesOromo[(t.Month() - 1)])[:1])
}
// localMonthsNamePashto returns the Pashto name of the month.
func localMonthsNamePashto(t time.Time, abbr int) string {
if int(t.Month()) == 6 {
if abbr == 3 {
return "\u0686\u0646\u06AB\u0627 \u069A"
}
if abbr == 4 || abbr > 6 {
return "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649"
}
}
return monthNamesPashto[int(t.Month())-1]
}
// localMonthsNamePersian returns the Persian name of the month.
func localMonthsNamePersian(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesPersian[(t.Month() - 1)])[:1])
}
return monthNamesPersian[int(t.Month())-1]
}
// localMonthsNamePolish returns the Polish name of the month.
func localMonthsNamePolish(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesPolish[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesPolish[int(t.Month())-1]
}
return string([]rune(monthNamesPolish[(t.Month() - 1)])[:1])
}
// localMonthsNamePortuguese returns the Portuguese name of the month.
func localMonthsNamePortuguese(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesPortuguese[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesPortuguese[int(t.Month())-1]
}
return string([]rune(monthNamesPortuguese[(t.Month() - 1)])[:1])
}
// localMonthsNamePunjabi returns the Punjabi name of the month.
func localMonthsNamePunjabi(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesPunjabi[(t.Month() - 1)])[:1])
}
return monthNamesPunjabi[int(t.Month())-1]
}
// localMonthsNamePunjabiArab returns the Punjabi Arab name of the month.
func localMonthsNamePunjabiArab(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesPunjabiArab[(t.Month() - 1)])[:1])
}
return monthNamesPunjabiArab[int(t.Month())-1]
}
// localMonthsNameQuechua returns the Quechua name of the month.
func localMonthsNameQuechua(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesQuechua[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesQuechua[int(t.Month())-1]
}
return string([]rune(monthNamesQuechua[(t.Month() - 1)])[:1])
}
// localMonthsNameQuechuaEcuador returns the QuechuaEcuador name of the month.
func localMonthsNameQuechuaEcuador(t time.Time, abbr int) string {
if abbr == 3 {
if int(t.Month()) == 1 {
return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:4])
}
return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesQuechuaEcuador[int(t.Month())-1]
}
return string([]rune(monthNamesQuechuaEcuador[(t.Month() - 1)])[:1])
}
// localMonthsNameRomanian returns the Romanian name of the month.
func localMonthsNameRomanian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesRomanianAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesRomanian[int(t.Month())-1]
}
return string([]rune(monthNamesRomanian[(t.Month() - 1)])[:1])
}
// localMonthsNameRomansh returns the Romansh name of the month.
func localMonthsNameRomansh(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesRomanshAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesRomansh[int(t.Month())-1]
}
return string([]rune(monthNamesRomansh[(t.Month() - 1)])[:1])
}
// localMonthsNameRussian returns the Russian name of the month.
func localMonthsNameRussian(t time.Time, abbr int) string {
if abbr == 3 {
month := monthNamesRussian[int(t.Month())-1]
if len([]rune(month)) <= 4 {
return month
}
return monthNamesRussianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesRussian[int(t.Month())-1]
}
return string([]rune(monthNamesRussian[int(t.Month())-1])[:1])
}
// localMonthsNameSakha returns the Sakha name of the month.
func localMonthsNameSakha(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSakhaAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesSakha[int(t.Month())-1]
}
return string([]rune(monthNamesSakha[(t.Month() - 1)])[:1])
}
// localMonthsNameSami returns the Sami name of the month.
func localMonthsNameSami(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSamiAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesSami[int(t.Month())-1]
}
return string([]rune(monthNamesSami[(t.Month() - 1)])[:1])
}
// localMonthsNameSamiLule returns the Sami (Lule) name of the month.
func localMonthsNameSamiLule(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSamiLuleAbbr[int(t.Month())-1]
}
if abbr == 4 || abbr > 6 {
return monthNamesSamiLule[int(t.Month())-1]
}
return string([]rune(monthNamesSamiLule[(t.Month() - 1)])[:1])
}
// localMonthsNameSamiNorthern returns the Sami (Northern) name of the month.
func localMonthsNameSamiNorthern(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSamiNorthernAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSamiNorthern[int(t.Month())-1]
}
return string([]rune(monthNamesSamiNorthern[(t.Month() - 1)])[:1])
}
// localMonthsNameSamiNorthernFI returns the Sami (Northern) Finland name of the
// month.
func localMonthsNameSamiNorthernFI(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSamiNorthernAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
if int(t.Month()) == 1 {
return "ođđajagemánu"
}
return monthNamesSamiNorthern[int(t.Month())-1]
}
return string([]rune(monthNamesSamiNorthern[(t.Month() - 1)])[:1])
}
// localMonthsNameSamiSkolt returns the Sami (Skolt) name of the month.
func localMonthsNameSamiSkolt(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesSamiSkolt[(t.Month() - 1)])[:1])
}
return monthNamesSamiSkolt[int(t.Month())-1]
}
// localMonthsNameSamiSouthern returns the Sami (Southern) name of the month.
func localMonthsNameSamiSouthern(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSamiSouthernAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSamiSouthern[int(t.Month())-1]
}
return string([]rune(monthNamesSamiSouthern[(t.Month() - 1)])[:1])
}
// localMonthsNameSanskrit returns the Sanskrit name of the month.
func localMonthsNameSanskrit(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesSanskrit[(t.Month() - 1)])[:1])
}
return monthNamesSanskrit[int(t.Month())-1]
}
// localMonthsNameScottishGaelic returns the Scottish Gaelic name of the month.
func localMonthsNameScottishGaelic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesScottishGaelicAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesScottishGaelic[int(t.Month())-1]
}
return string([]rune(monthNamesScottishGaelic[(t.Month() - 1)])[:1])
}
// localMonthsNameSerbian returns the Serbian (Cyrillic) name of the month.
func localMonthsNameSerbian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSerbianAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSerbian[int(t.Month())-1]
}
return string([]rune(monthNamesSerbian[(t.Month() - 1)])[:1])
}
// localMonthsNameSerbianBA returns the Serbian (Cyrillic) Bosnia and
// Herzegovina name of the month.
func localMonthsNameSerbianBA(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSerbianBAAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSerbianBA[int(t.Month())-1]
}
return string([]rune(monthNamesSerbianBA[(t.Month() - 1)])[:1])
}
// localMonthsNameSerbianLatin returns the Serbian (Latin) name of the month.
func localMonthsNameSerbianLatin(t time.Time, abbr int) string {
if abbr == 3 {
return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:3])
}
if abbr == 4 || abbr > 6 {
return monthNamesSerbianLatin[int(t.Month())-1]
}
return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:1])
}
// localMonthsNameSerbianLatinCS returns the Serbian (Latin) name of the month.
func localMonthsNameSerbianLatinCS(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSerbianLatinAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSerbianLatin[int(t.Month())-1]
}
return string([]rune(monthNamesSerbianLatin[(t.Month() - 1)])[:1])
}
// localMonthsNameSesothoSaLeboa returns the Sesotho sa Leboa name of the month.
func localMonthsNameSesothoSaLeboa(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSesothoSaLeboaAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSesothoSaLeboa[int(t.Month())-1]
}
return string([]rune(monthNamesSesothoSaLeboa[(t.Month() - 1)])[:1])
}
// localMonthsNameSetswana returns the Setswana name of the month.
func localMonthsNameSetswana(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSetswanaAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSetswana[int(t.Month())-1]
}
return string([]rune(monthNamesSetswana[(t.Month() - 1)])[:1])
}
// localMonthsNameSindhi returns the Sindhi name of the month.
func localMonthsNameSindhi(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesSindhi[(t.Month() - 1)])[:1])
}
return monthNamesSindhi[int(t.Month())-1]
}
// localMonthsNameSinhala returns the Sinhala name of the month.
func localMonthsNameSinhala(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSinhalaAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSinhala[int(t.Month())-1]
}
return string([]rune(monthNamesSinhala[(t.Month() - 1)])[:1])
}
// localMonthsNameSlovak returns the Slovak name of the month.
func localMonthsNameSlovak(t time.Time, abbr int) string {
if abbr == 3 {
return strconv.Itoa(int(t.Month()))
}
if abbr == 4 || abbr > 6 {
return monthNamesSlovak[int(t.Month())-1]
}
return string([]rune(monthNamesSlovak[(t.Month() - 1)])[:1])
}
// localMonthsNameSlovenian returns the Slovenian name of the month.
func localMonthsNameSlovenian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSlovenianAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSlovenian[int(t.Month())-1]
}
return string([]rune(monthNamesSlovenian[(t.Month() - 1)])[:1])
}
// localMonthsNameSomali returns the Somali name of the month.
func localMonthsNameSomali(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSomaliAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSomali[int(t.Month())-1]
}
return string([]rune(monthNamesSomali[(t.Month() - 1)])[:1])
}
// localMonthsNameSotho returns the Sotho name of the month.
func localMonthsNameSotho(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSothoAbbr[(t.Month() - 1)]
}
if abbr == 4 || abbr > 6 {
return monthNamesSotho[int(t.Month())-1]
}
return string([]rune(monthNamesSotho[(t.Month() - 1)])[:1])
}
// localMonthsNameSpanish returns the Spanish name of the month.
func localMonthsNameSpanish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSpanishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSpanish[int(t.Month())-1]
}
return monthNamesSpanishAbbr[int(t.Month())-1][:1]
}
// localMonthsNameSpanishPE returns the Spanish Peru name of the month.
func localMonthsNameSpanishPE(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSpanishPEAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSpanishPE[int(t.Month())-1]
}
return monthNamesSpanishPEAbbr[int(t.Month())-1][:1]
}
// localMonthsNameSwedish returns the Swedish name of the month.
func localMonthsNameSwedish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSwedishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSwedish[int(t.Month())-1]
}
return monthNamesSwedishAbbr[int(t.Month())-1][:1]
}
// localMonthsNameSwedishFI returns the Swedish Finland name of the month.
func localMonthsNameSwedishFI(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSwedishFIAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSwedish[int(t.Month())-1]
}
return monthNamesSwedishFIAbbr[int(t.Month())-1][:1]
}
// localMonthsNameSyriac returns the Syriac name of the month.
func localMonthsNameSyriac(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSyriacAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSyriac[int(t.Month())-1]
}
return string([]rune(monthNamesSyriac[(t.Month() - 1)])[:1])
}
// localMonthsNameTajik returns the Tajik name of the month.
func localMonthsNameTajik(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTajikAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTajik[int(t.Month())-1]
}
return string([]rune(monthNamesTajik[(t.Month() - 1)])[:1])
}
// localMonthsNameTamazight returns the Tamazight name of the month.
func localMonthsNameTamazight(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTamazightAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTamazight[int(t.Month())-1]
}
return string([]rune(monthNamesTamazight[(t.Month() - 1)])[:1])
}
// localMonthsNameTamil returns the Tamil name of the month.
func localMonthsNameTamil(t time.Time, abbr int) string {
if abbr == 5 {
return string([]rune(monthNamesTamil[(t.Month() - 1)])[:1])
}
return monthNamesTamil[int(t.Month())-1]
}
// localMonthsNameTamilLK returns the Tamil Sri Lanka name of the month.
func localMonthsNameTamilLK(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTamilAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTamil[int(t.Month())-1]
}
return string([]rune(monthNamesTamil[(t.Month() - 1)])[:1])
}
// localMonthsNameTatar returns the Tatar name of the month.
func localMonthsNameTatar(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTatarAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTatar[int(t.Month())-1]
}
return string([]rune(monthNamesTatar[(t.Month() - 1)])[:1])
}
// localMonthsNameTelugu returns the Telugu name of the month.
func localMonthsNameTelugu(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTeluguAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTelugu[int(t.Month())-1]
}
return string([]rune(monthNamesTelugu[(t.Month() - 1)])[:1])
}
// localMonthsNameSyllabics returns the Syllabics name of the month.
func localMonthsNameSyllabics(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesSyllabicsAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesSyllabics[int(t.Month())-1]
}
return string([]rune(monthNamesSyllabics[int(t.Month())-1])[:1])
}
// localMonthsNameThai returns the Thai name of the month.
func localMonthsNameThai(t time.Time, abbr int) string {
if abbr == 3 {
r := []rune(monthNamesThai[int(t.Month())-1])
return string(r[:1]) + "." + string(r[len(r)-2:len(r)-1]) + "."
}
if abbr == 4 {
return monthNamesThai[int(t.Month())-1]
}
return string([]rune(monthNamesThai[int(t.Month())-1])[:1])
}
// localMonthsNameTibetan returns the Tibetan name of the month.
func localMonthsNameTibetan(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTibetanAbbr[int(t.Month())-1]
}
if abbr == 5 {
if t.Month() == 10 {
return "\u0F66"
}
return "\u0F5F"
}
return monthNamesTibetan[int(t.Month())-1]
}
// localMonthsNameTigrinya returns the Tigrinya name of the month.
func localMonthsNameTigrinya(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTigrinyaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTigrinya[int(t.Month())-1]
}
return string([]rune(monthNamesTigrinya[(t.Month() - 1)])[:1])
}
// localMonthsNameTsonga returns the Tsonga name of the month.
func localMonthsNameTsonga(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTsongaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTsonga[int(t.Month())-1]
}
return string([]rune(monthNamesTsonga[(t.Month() - 1)])[:1])
}
// localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of
// the month.
func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string {
if abbr == 5 {
return "M"
}
return monthNamesTradMongolian[t.Month()-1]
}
// localMonthsNameTurkish returns the Turkish name of the month.
func localMonthsNameTurkish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTurkishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTurkish[int(t.Month())-1]
}
return string([]rune(monthNamesTurkishAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameTurkmen returns the Turkmen name of the month.
func localMonthsNameTurkmen(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTurkmenAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTurkmen[int(t.Month())-1]
}
return string([]rune(monthNamesTurkmenAbbr[int(t.Month())-1])[:1])
}
// localMonthsNameUkrainian returns the Ukrainian name of the month.
func localMonthsNameUkrainian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesUkrainianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesUkrainian[int(t.Month())-1]
}
return string([]rune(monthNamesUkrainian[int(t.Month())-1])[:1])
}
// localMonthsNameUpperSorbian returns the Upper Sorbian name of the month.
func localMonthsNameUpperSorbian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesUpperSorbianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesUpperSorbian[int(t.Month())-1]
}
return string([]rune(monthNamesUpperSorbian[int(t.Month())-1])[:1])
}
// localMonthsNameUyghur returns the Uyghur name of the month.
func localMonthsNameUyghur(t time.Time, abbr int) string {
if abbr == 3 {
return fmt.Sprintf("%d-\u0626\u0627\u064A", int(t.Month()))
}
if abbr == 4 {
return monthNamesUyghur[int(t.Month())-1]
}
return string([]rune(monthNamesUyghur[int(t.Month())-1])[:1])
}
// localMonthsNameUzbek returns the Uzbek name of the month.
func localMonthsNameUzbek(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesUzbekAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesUzbek[int(t.Month())-1]
}
return string([]rune(monthNamesUzbek[int(t.Month())-1])[:1])
}
// localMonthsNameUzbekCyrillic returns the Uzbek (Cyrillic) name of the month.
func localMonthsNameUzbekCyrillic(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesTajikAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesTajik[int(t.Month())-1]
}
return string([]rune(monthNamesTajik[int(t.Month())-1])[:1])
}
// localMonthsNameValencian returns the Valencian name of the month.
func localMonthsNameValencian(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesValencianAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesValencian[int(t.Month())-1]
}
return string([]rune(monthNamesValencian[int(t.Month())-1])[:1])
}
// localMonthsNameVenda returns the Venda name of the month.
func localMonthsNameVenda(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesVendaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesVenda[int(t.Month())-1]
}
return string([]rune(monthNamesVenda[int(t.Month())-1])[:1])
}
// localMonthsNameVietnamese returns the Vietnamese name of the month.
func localMonthsNameVietnamese(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesVietnameseAbbr3[t.Month()-1]
}
if abbr == 5 {
return monthNamesVietnameseAbbr5[t.Month()-1]
}
return monthNamesVietnamese[t.Month()-1]
}
// localMonthsNameWelsh returns the Welsh name of the month.
func localMonthsNameWelsh(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesWelshAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesWelsh[int(t.Month())-1]
}
return monthNamesWelshAbbr[int(t.Month())-1][:1]
}
// localMonthsNameWolof returns the Wolof name of the month.
func localMonthsNameWolof(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesWolofAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesWolof[int(t.Month())-1]
}
return monthNamesWolof[int(t.Month())-1][:1]
}
// localMonthsNameXhosa returns the Xhosa name of the month.
func localMonthsNameXhosa(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesXhosaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesXhosa[int(t.Month())-1]
}
return "u"
}
// localMonthsNameYi returns the Yi name of the month.
func localMonthsNameYi(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
return monthNamesYiSuffix[t.Month()-1]
}
return string([]rune(monthNamesYi[int(t.Month())-1])[:1])
}
// localMonthsNameYiddish returns the Yiddish name of the month.
func localMonthsNameYiddish(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesYiddishAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesYiddish[int(t.Month())-1]
}
return string([]rune(monthNamesYiddish[int(t.Month())-1])[:1])
}
// localMonthsNameYoruba returns the Yoruba name of the month.
func localMonthsNameYoruba(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesYorubaAbbr[int(t.Month())-1]
}
if abbr == 4 {
return monthNamesYoruba[int(t.Month())-1]
}
return string([]rune(monthNamesYoruba[int(t.Month())-1])[:1])
}
// localMonthsNameZulu returns the Zulu name of the month.
func localMonthsNameZulu(t time.Time, abbr int) string {
if abbr == 3 {
return monthNamesZuluAbbr[t.Month()-1]
}
if abbr == 4 {
return monthNamesZulu[int(t.Month())-1]
}
return monthNamesZuluAbbr[int(t.Month())-1][:1]
}
// localMonthsName return months name by supported language ID.
func (nf *numberFormat) localMonthsName(abbr int) string {
t := nf.t
if nf.number < 1 {
t = timeFromExcelTime(nf.number+2, nf.date1904)
}
if 1 <= nf.number && nf.number < 60 {
t = timeFromExcelTime(nf.number+1, nf.date1904)
}
if languageInfo, ok := getSupportedLanguageInfo(nf.localCode); ok {
return languageInfo.localMonth(t, abbr)
}
return localMonthsNameEnglish(t, abbr)
}
// dateAmPmHandler will be handling am/pm types tokens for a number format.
func (nf *numberFormat) dateAmPmHandler(i int, token nfp.Token) {
if nf.ap == "" {
nextHours := nf.hoursNext(i)
aps := strings.Split(nf.localAmPm(token.TValue), "/")
nf.ap = aps[0]
if nextHours >= 12 {
nf.ap = aps[1]
}
}
nf.result += nf.ap
}
// dateTimesHandler will be handling date and times types tokens for a number
// format expression.
func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) {
if idx := inStrSlice(nfp.AmPm, strings.ToUpper(token.TValue), false); idx != -1 {
nf.dateAmPmHandler(i, token)
return
}
if strings.Contains(strings.ToUpper(token.TValue), "M") {
m := int(nf.t.Month())
if nf.number < 2 {
m = 1
}
if 60 <= nf.number && nf.number < 61 {
m = 2
}
l := len(token.TValue)
if l == 1 && nf.isMonthToken(i) {
nf.result += strconv.Itoa(int(m))
return
}
if l == 2 && nf.isMonthToken(i) {
nf.result += fmt.Sprintf("%02d", int(m))
return
}
if l == 3 {
nf.result += nf.localMonthsName(3)
return
}
if l == 4 || l > 5 {
nf.result += nf.localMonthsName(4)
return
}
if l == 5 {
nf.result += nf.localMonthsName(5)
return
}
}
nf.yearsHandler(token)
nf.daysHandler(token)
nf.hoursHandler(i, token)
nf.minutesHandler(token)
nf.secondsHandler(token)
}
// eraYear convert time to the Japanese era years.
func eraYear(t time.Time) (int, int) {
i, year := 0, -1
for i = len(japaneseEraYears) - 1; i > 0; i-- {
if y := japaneseEraYears[i]; t.After(y) {
year = t.Year() - y.Year() + 1
break
}
}
return i, year
}
// japaneseYearHandler handling the Japanease calendar years.
func (nf *numberFormat) japaneseYearHandler(token nfp.Token, langInfo languageInfo) {
if strings.Contains(strings.ToUpper(token.TValue), "G") {
i, year := eraYear(nf.t)
if year == -1 {
return
}
nf.useGannen = langInfo.useGannen
switch len(token.TValue) {
case 1:
nf.useGannen = false
nf.result += japaneseEraSymbols[i]
case 2:
nf.result += japaneseEraNames[i][:3]
default:
nf.result += japaneseEraNames[i]
}
}
if strings.Contains(strings.ToUpper(token.TValue), "E") {
_, year := eraYear(nf.t)
if year == -1 {
nf.result += strconv.Itoa(nf.t.Year())
return
}
if year == 1 && nf.useGannen {
nf.result += "\u5143"
return
}
if len(token.TValue) == 1 && !nf.useGannen {
nf.result += strconv.Itoa(year)
return
}
if len(token.TValue) == 2 {
nf.result += fmt.Sprintf("%02d", year)
}
}
}
// republicOfChinaYearHandler handling the Republic of China calendar years.
func (nf *numberFormat) republicOfChinaYearHandler(token nfp.Token, langInfo languageInfo) {
year := nf.t.Year()
if nf.number < 2 {
year = 1900
}
if strings.Contains(strings.ToUpper(token.TValue), "G") {
year = year - republicOfChinaYear.Year() + 1
if year == 1 {
nf.useGannen = langInfo.useGannen
}
var name string
if name = republicOfChinaEraName[0]; len(token.TValue) < 3 {
name = republicOfChinaEraName[1]
}
if year < 0 {
name += republicOfChinaEraName[2]
}
nf.result += name
}
if strings.Contains(strings.ToUpper(token.TValue), "E") {
year = year - republicOfChinaYear.Year() + 1
if year < 0 {
year = republicOfChinaYear.Year() - nf.t.Year()
}
if year == 1 && nf.useGannen {
nf.result += "\u5143"
return
}
if len(token.TValue) == 1 && !nf.useGannen {
nf.result += strconv.Itoa(year)
}
}
}
// yearsHandler will be handling years in the date and times types tokens for a
// number format expression.
func (nf *numberFormat) yearsHandler(token nfp.Token) {
langInfo, _ := getSupportedLanguageInfo(nf.localCode)
year := nf.t.Year()
if nf.number < 2 {
year = 1900
}
if strings.Contains(strings.ToUpper(token.TValue), "Y") {
if nf.opts != nil && nf.opts.CultureInfo == CultureNameKoKR {
year += 2333
}
if len(token.TValue) <= 2 {
nf.result += strconv.Itoa(year)[2:]
return
}
nf.result += strconv.Itoa(year)
return
}
if inStrSlice(langInfo.tags, "zh-TW", false) != -1 ||
nf.opts != nil && nf.opts.CultureInfo == CultureNameZhTW {
nf.republicOfChinaYearHandler(token, langInfo)
return
}
if inStrSlice(langInfo.tags, "ja-JP", false) != -1 ||
nf.opts != nil && nf.opts.CultureInfo == CultureNameJaJP {
nf.japaneseYearHandler(token, langInfo)
return
}
if strings.Contains(strings.ToUpper(token.TValue), "E") {
nf.result += strconv.Itoa(year)
return
}
}
// daysHandler will be handling days in the date and times types tokens for a
// number format expression.
func (nf *numberFormat) daysHandler(token nfp.Token) {
info, _ := getSupportedLanguageInfo(nf.localCode)
l := len(token.TValue)
weekdayNames, weekdayNamesAbbr := info.weekdayNames, info.weekdayNamesAbbr
if len(weekdayNames) != 7 {
weekdayNames = weekdayNamesEnglish
}
if len(weekdayNamesAbbr) != 7 {
weekdayNamesAbbr = weekdayNamesEnglishAbbr
}
if strings.Contains(strings.ToUpper(token.TValue), "A") {
if l == 3 {
nf.result += weekdayNamesAbbr[nf.t.Weekday()]
}
if l > 3 {
nf.result += weekdayNames[nf.t.Weekday()]
}
return
}
if strings.Contains(strings.ToUpper(token.TValue), "D") {
d := nf.t.Day()
if nf.number < 1 {
d = 0
}
if 1 <= nf.number && nf.number < 60 {
d = timeFromExcelTime(nf.number+1, nf.date1904).Day()
}
if 60 <= nf.number && nf.number < 61 {
d = 29
}
switch l {
case 1:
nf.result += strconv.Itoa(d)
case 2:
nf.result += fmt.Sprintf("%02d", d)
case 3:
nf.result += weekdayNamesAbbr[nf.t.Weekday()]
default:
nf.result += weekdayNames[nf.t.Weekday()]
}
}
}
// hoursHandler will be handling hours in the date and times types tokens for a
// number format expression.
func (nf *numberFormat) hoursHandler(i int, token nfp.Token) {
if nf.hours = strings.Contains(strings.ToUpper(token.TValue), "H"); nf.hours {
h := nf.t.Hour()
ap, ok := nf.apNext(i)
if ok {
nf.ap = ap[0]
if h >= 12 {
nf.ap = ap[1]
}
if h > 12 {
h -= 12
}
}
if nf.ap != "" {
if nf.hoursNext(i) == -1 && h > 12 {
h -= 12
}
if h == 0 {
h = 12
}
}
switch len(token.TValue) {
case 1:
nf.result += strconv.Itoa(h)
return
default:
nf.result += fmt.Sprintf("%02d", h)
return
}
}
}
// minutesHandler will be handling minutes in the date and times types tokens
// for a number format expression.
func (nf *numberFormat) minutesHandler(token nfp.Token) {
if strings.Contains(strings.ToUpper(token.TValue), "M") {
nf.hours = false
switch len(token.TValue) {
case 1:
nf.result += strconv.Itoa(nf.t.Minute())
return
default:
nf.result += fmt.Sprintf("%02d", nf.t.Minute())
}
}
}
// secondsHandler will be handling seconds in the date and times types tokens
// for a number format expression.
func (nf *numberFormat) secondsHandler(token nfp.Token) {
if nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S"); !nf.seconds {
return
}
if len(token.TValue) == 1 {
nf.result += strconv.Itoa(nf.t.Second())
return
}
nf.result += fmt.Sprintf("%02d", nf.t.Second())
}
// elapsedDateTimesHandler will be handling elapsed date and times types tokens
// for a number format expression.
func (nf *numberFormat) elapsedDateTimesHandler(token nfp.Token) {
if strings.Contains(strings.ToUpper(token.TValue), "H") {
nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Hours()))
return
}
if strings.Contains(strings.ToUpper(token.TValue), "M") {
nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Minutes()))
return
}
if strings.Contains(strings.ToUpper(token.TValue), "S") {
nf.result += fmt.Sprintf("%.f", math.Floor(nf.t.Sub(excel1900Epoc).Seconds()))
return
}
}
// hoursNext detects if a token of type hours exists after a given tokens list.
func (nf *numberFormat) hoursNext(i int) int {
tokens := nf.section[nf.sectionIdx].Items
for idx := i + 1; idx < len(tokens); idx++ {
if tokens[idx].TType == nfp.TokenTypeDateTimes {
if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") {
t := timeFromExcelTime(nf.number, false)
return t.Hour()
}
}
}
return -1
}
// apNext detects if a token of type AM/PM exists after a given tokens list.
func (nf *numberFormat) apNext(i int) ([]string, bool) {
tokens := nf.section[nf.sectionIdx].Items
for idx := i + 1; idx < len(tokens); idx++ {
if tokens[idx].TType == nfp.TokenTypeDateTimes {
if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") {
return nil, false
}
if i := inStrSlice(nfp.AmPm, tokens[idx].TValue, false); i != -1 {
return strings.Split(nf.localAmPm(tokens[idx].TValue), "/"), true
}
}
}
return nil, false
}
// isMonthToken detects if the given token represents minutes, if no hours and
// seconds tokens before the given token or not seconds after the given token,
// the current token is a minutes token.
func (nf *numberFormat) isMonthToken(i int) bool {
tokens := nf.section[nf.sectionIdx].Items
var timePrevious, secondsNext bool
for idx := i - 1; idx >= 0; idx-- {
if tokens[idx].TType == nfp.TokenTypeDateTimes {
timePrevious = strings.ContainsAny(strings.ToUpper(tokens[idx].TValue), "HS")
break
}
if tokens[idx].TType == nfp.TokenTypeElapsedDateTimes {
timePrevious = true
break
}
}
for idx := i + 1; idx < len(tokens); idx++ {
if tokens[idx].TType == nfp.TokenTypeDateTimes {
secondsNext = strings.Contains(strings.ToUpper(tokens[idx].TValue), "S")
break
}
}
return !timePrevious && !secondsNext
}
// negativeHandler will be handling negative selection for a number format
// expression.
func (nf *numberFormat) negativeHandler() (result string) {
for _, token := range nf.section[nf.sectionIdx].Items {
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
return nf.value
}
if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 {
return nf.value
}
}
return nf.numberHandler()
}
// textHandler will be handling text selection for a number format expression.
func (nf *numberFormat) textHandler() (result string) {
for _, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeLiteral {
result += token.TValue
}
if token.TType == nfp.TokenTypeTextPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder {
result += nf.value
}
}
return result
}
// getValueSectionType returns its applicable number format expression section
// based on the given value.
func (nf *numberFormat) getValueSectionType(value string) (float64, string) {
if nf.cellType != CellTypeNumber && nf.cellType != CellTypeDate {
return 0, nfp.TokenSectionText
}
isNum, _, _ := isNumeric(value)
if !isNum {
return 0, nfp.TokenSectionText
}
number, _ := strconv.ParseFloat(value, 64)
if number >= 0 {
return number, nfp.TokenSectionPositive
}
var hasNeg bool
for _, sec := range nf.section {
if sec.Type == nfp.TokenSectionNegative {
hasNeg = true
}
}
if !hasNeg {
nf.usePositive = true
return number, nfp.TokenSectionPositive
}
return number, nfp.TokenSectionNegative
}
================================================
FILE: numfmt_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/xuri/nfp"
)
func TestNumFmt(t *testing.T) {
for _, item := range [][]string{
{"123", "general", "123"},
{"-123", ";general", "-123"},
{"123.456", "0.00\"\"", "123.46"},
{"123.456", "\"\";\"\"", ""},
{"123.456", "\"\"0.00foo", "123.46foo"},
{"123.456", "\"\"0.00\"foo\"", "123.46foo"},
{"12345678901", "General", "12345678901"},
{"43543.5448726851", "General", "43543.54487"},
{"-43543.5448726851", "General", "-43543.54487"},
{"1234567890.12345", "General", "1234567890"},
{"43528", "y", "19"},
{"43528", "Y", "19"},
{"43528", "yy", "19"},
{"43528", "YY", "19"},
{"43528", "yyy", "2019"},
{"43528", "YYY", "2019"},
{"43528", "yyyy", "2019"},
{"43528", "YYYY", "2019"},
{"43528", "yyyyy", "2019"},
{"43528", "YYYYY", "2019"},
{"43528", "m", "3"},
{"43528", "mm", "03"},
{"43528", "mmm", "Mar"},
{"43528", "mmmm", "March"},
{"43528", "mmmmm", "M"},
{"43528", "mmmmmm", "March"},
{"43528", "d", "4"},
{"43528", "dd", "04"},
{"43528", "ddd", "Mon"},
{"43528", "dddd", "Monday"},
{"43528", "h", "0"},
{"43528", "hh", "00"},
{"43528", "hhh", "00"},
{"43543.544872685183", "hhmm", "1304"},
{"43543.544872685183", "mmhhmmmm", "0313March"},
{"43543.544872685183", "mm hh mm mm", "03 13 04 03"},
{"43543.544872685183", "mm hh m m", "03 13 4 3"},
{"43543.544872685183", "m s", "4 37"},
{"43528", "[h]", "1044672"},
{"43528", "[m]", "62680320"},
{"43528", "s", "0"},
{"43528", "ss", "00"},
{"43528", "[s]", "3760819200"},
{"43543.544872685183", "h:mm:ss AM/PM", "1:04:37 PM"},
{"43543.544872685183", "AM/PM h:mm:ss", "PM 1:04:37"},
{"43543.086539351854", "hh:mm:ss AM/PM", "02:04:37 AM"},
{"43543.086539351854", "AM/PM hh:mm:ss", "AM 02:04:37"},
{"43543.086539351854", "AM/PM hh:mm:ss a/p", "AM 02:04:37 a"},
{"0.609375", "[HH]:mm:ss", "14:37:30"},
{"43528", "YYYY", "2019"},
{"43528", "", "43528"},
{"43528.2123", "YYYY-MM-DD hh:mm:ss", "2019-03-04 05:05:43"},
{"43528.2123", "YYYY-MM-DD hh:mm:ss;YYYY-MM-DD hh:mm:ss", "2019-03-04 05:05:43"},
{"43528.2123", "M/D/YYYY h:m:s", "3/4/2019 5:5:43"},
{"43528.003958333335", "m/d/yyyy h:m:s", "3/4/2019 0:5:42"},
{"43528.003958333335", "M/D/YYYY h:mm:s", "3/4/2019 0:05:42"},
{"0.64583333333333337", "h:mm:ss am/pm", "3:30:00 pm"},
{"43528.003958333335", "h:mm", "0:05"},
{"6.9444444444444444E-5", "h:m", "0:0"},
{"6.9444444444444444E-5", "h:mm", "0:00"},
{"6.9444444444444444E-5", "h:m", "0:0"},
{"0.50070601851851848", "h:m", "12:1"},
{"0.97952546296296295", "h:m", "23:30"},
{"43528", "mmmm", "March"},
{"43528", "dddd", "Monday"},
{"0", ";;;", ""},
{"0", "0%", "0%"},
{"0", "0.0%", "0.0%"},
{"0", "0.00%", "0.00%"},
{"0", "[$-zh-TW]yyyy\"年\"m\"月\"d\"日\";@", "1900年1月0日"},
{"60", "yyyy-mm-dd;@", "1900-02-29"},
{"43528", "[$-409]MM/DD/YYYY", "03/04/2019"},
{"43528", "[$-409]MM/DD/YYYY am/pm", "03/04/2019 AM"},
{"43528", "[$-111]MM/DD/YYYY", "43528"},
{"43528", "[$US-409]MM/DD/YYYY", "US03/04/2019"},
{"43543.586539351854", "AM/PM h h:mm", "PM 14 2:04"},
{"45186", "DD.MM.YYYY", "17.09.2023"},
{"text", "AM/PM h h:mm", "text"},
{"43466.189571759256", "[$-404]aaa;@", "週二"},
{"43466.189571759256", "[$-404]aaaa;@", "星期二"},
{"43466.189571759256", "[$-zh-TW]aaa;@", "週二"},
{"43466.189571759256", "[$-zh-TW]aaaa;@", "星期二"},
{"43466.189571759256", "[$-804]aaa;@", "周二"},
{"43466.189571759256", "[$-804]aaaa;@", "星期二"},
{"43466.189571759256", "[$-0804]aaa;@", "周二"},
{"43466.189571759256", "[$-0804]aaaa;@", "星期二"},
{"43466.189571759256", "[$-zh-CN]aaa;@", "周二"},
{"43466.189571759256", "[$-zh-CN]aaaa;@", "星期二"},
{"43466.189571759256", "[$-435]aaa;@", "Bi."},
{"43466.189571759256", "[$-435]aaaa;@", "ULwesibili"},
{"43466.189571759256", "[$-0435]aaa;@", "Bi."},
{"43466.189571759256", "[$-0435]aaaa;@", "ULwesibili"},
{"43466.189571759256", "[$-zu-ZA]aaa;@", "Bi."},
{"43466.189571759256", "[$-zu-ZA]aaaa;@", "ULwesibili"},
{"43466.189571759256", "[$-404]ddd;@", "週二"},
{"43466.189571759256", "[$-404]dddd;@", "星期二"},
{"43466.189571759256", "[$-0404]ddd;@", "週二"},
{"43466.189571759256", "[$-0404]dddd;@", "星期二"},
{"43466.189571759256", "[$-zh-TW]ddd;@", "週二"},
{"43466.189571759256", "[$-zh-TW]dddd;@", "星期二"},
{"43466.189571759256", "[$-804]ddd;@", "周二"},
{"43466.189571759256", "[$-804]dddd;@", "星期二"},
{"43466.189571759256", "[$-0804]ddd;@", "周二"},
{"43466.189571759256", "[$-0804]dddd;@", "星期二"},
{"43466.189571759256", "[$-zh-CN]ddd;@", "周二"},
{"43466.189571759256", "[$-zh-CN]dddd;@", "星期二"},
{"43466.189571759256", "[$-435]ddd;@", "Bi."},
{"43466.189571759256", "[$-435]dddd;@", "ULwesibili"},
{"43466.189571759256", "[$-0435]ddd;@", "Bi."},
{"43466.189571759256", "[$-0435]dddd;@", "ULwesibili"},
{"43466.189571759256", "[$-zu-ZA]ddd;@", "Bi."},
{"43466.189571759256", "[$-zu-ZA]dddd;@", "ULwesibili"},
{"44562.189571759256", "[$-36]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
{"44562.189571759256", "[$-36]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
{"44562.189571759256", "[$-36]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
{"44682.18957170139", "[$-36]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
{"44682.18957170139", "[$-36]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
{"44682.18957170139", "[$-36]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
{"44562.189571759256", "[$-036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
{"44562.189571759256", "[$-036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
{"44562.189571759256", "[$-036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
{"44682.18957170139", "[$-036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
{"44682.18957170139", "[$-036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
{"44682.18957170139", "[$-036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
{"44562.189571759256", "[$-0036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
{"44562.189571759256", "[$-0036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
{"44562.189571759256", "[$-0036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
{"44682.18957170139", "[$-0036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
{"44682.18957170139", "[$-0036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
{"44682.18957170139", "[$-0036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
{"44562.189571759256", "[$-af]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
{"44562.189571759256", "[$-af]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
{"44562.189571759256", "[$-af]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
{"44682.18957170139", "[$-af]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
{"44682.18957170139", "[$-af]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
{"44682.18957170139", "[$-af]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
{"44562.189571759256", "[$-436]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
{"44562.189571759256", "[$-436]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
{"44562.189571759256", "[$-436]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
{"44682.18957170139", "[$-436]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
{"44682.18957170139", "[$-436]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
{"44682.18957170139", "[$-436]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
{"44562.189571759256", "[$-1C]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-1C]mmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-1C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-1C]mmmmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."},
{"43543.503206018519", "[$-1C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 m.d."},
{"43543.503206018519", "[$-1C]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 m.d. mar"},
{"43543.503206018519", "[$-1C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 m.d. mar"},
{"43543.503206018519", "[$-1C]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 m.d. e martë"},
{"44562.189571759256", "[$-41C]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-41C]mmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-41C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 p.d."},
{"44562.189571759256", "[$-41C]mmmmmm dd yyyy h:mm AM/PM", "janar 01 2022 4:32 p.d."},
{"43543.503206018519", "[$-41C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 m.d."},
{"43543.503206018519", "[$-41C]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 m.d. mar"},
{"43543.503206018519", "[$-41C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 m.d. mar"},
{"43543.503206018519", "[$-41C]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 m.d. e martë"},
{"44562.189571759256", "[$-84]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 vorm."},
{"44562.189571759256", "[$-84]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 vorm."},
{"44562.189571759256", "[$-84]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 vorm."},
{"44562.189571759256", "[$-84]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 vorm."},
{"43543.503206018519", "[$-84]mmm dd yyyy h:mm AM/PM", "Mär 19 2019 12:04 nam."},
{"43543.503206018519", "[$-84]mmmm dd yyyy h:mm AM/PM aaa", "März 19 2019 12:04 nam. Zi."},
{"43543.503206018519", "[$-84]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 nam. Zi."},
{"43543.503206018519", "[$-84]mmmmmm dd yyyy h:mm AM/PM dddd", "März 19 2019 12:04 nam. Ziischtig"},
{"44562.189571759256", "[$-484]mmm dd yyyy h:mm AM/PM", "Jän. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-484]mmmm dd yyyy h:mm AM/PM", "Jänner 01 2022 4:32 AM"},
{"44562.189571759256", "[$-484]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-484]mmmmmm dd yyyy h:mm AM/PM", "Jänner 01 2022 4:32 AM"},
{"43543.503206018519", "[$-484]mmm dd yyyy h:mm AM/PM", "März 19 2019 12:04 PM"},
{"43543.503206018519", "[$-484]mmmm dd yyyy h:mm AM/PM aaa", "März 19 2019 12:04 PM Zi."},
{"43543.503206018519", "[$-484]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Zi."},
{"43543.503206018519", "[$-484]mmmmmm dd yyyy h:mm AM/PM dddd", "März 19 2019 12:04 PM Zischti"},
{"44562.189571759256", "[$-5E]mmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-5E]mmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-5E]mmmmm dd yyyy h:mm AM/PM", "\u1303 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-5E]mmmmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"},
{"43543.503206018519", "[$-5E]mmm dd yyyy h:mm AM/PM", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275"},
{"43543.503206018519", "[$-5E]mmmm dd yyyy h:mm AM/PM aaa", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"},
{"43543.503206018519", "[$-5E]mmmmm dd yyyy h:mm AM/PM ddd", "\u121B 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"},
{"43543.503206018519", "[$-5E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230\u129E"},
{"44562.189571759256", "[$-45E]mmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-45E]mmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-45E]mmmmm dd yyyy h:mm AM/PM", "\u1303 01 2022 4:32 \u1325\u12CB\u1275"},
{"44562.189571759256", "[$-45E]mmmmmm dd yyyy h:mm AM/PM", "\u1303\u1295\u12E9\u12C8\u122A 01 2022 4:32 \u1325\u12CB\u1275"},
{"43543.503206018519", "[$-45E]mmm dd yyyy h:mm AM/PM", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275"},
{"43543.503206018519", "[$-45E]mmmm dd yyyy h:mm AM/PM aaa", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"},
{"43543.503206018519", "[$-45E]mmmmm dd yyyy h:mm AM/PM ddd", "\u121B 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230"},
{"43543.503206018519", "[$-45E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u121B\u122D\u127D 19 2019 12:04 \u12A8\u1230\u12D3\u1275 \u121B\u12AD\u1230\u129E"},
{"44562.189571759256", "[$-1]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-1401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-3C01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3C01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3C01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3C01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-3C01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-3C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-c01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-c01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-c01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-c01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-c01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-c01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-c01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-c01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-801]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-801]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-801]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-801]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-801]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-801]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-2C01]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2C01]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2C01]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2C01]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-2C01]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-2C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-3401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-3401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-3401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-3001]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3001]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3001]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3001]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-3001]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-3001]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-1001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-1801]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1801]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1801]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1801]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1801]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1801]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-2001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-2001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-2001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-4001]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-4001]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-4001]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-4001]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-4001]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-4001]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-4001]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-4001]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43466.189571759256", "[$-404]g\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5E74\u0031\u6708\u0031\u65E5"},
{"43466.189571759256", "[$-404]e\"年\"m\"月\"d\"日\";@", "\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"},
{"43466.189571759256", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"},
{"43466.189571759256", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"},
{"43466.189571759256", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"},
{"43466.189571759256", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u0031\u0030\u0038\u5E74\u0031\u6708\u0031\u65E5"},
{"4385.5083333333332", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"},
{"4385.5083333333332", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"},
{"4385.5083333333332", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"},
{"4385.5083333333332", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u5143\u5E74\u0031\u6708\u0032\u65E5"},
{"123", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"},
{"123", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"},
{"123", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"},
{"123", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4E2D\u83EF\u6C11\u570B\u524D\u0031\u0032\u5E74\u0035\u6708\u0032\u65E5"},
{"44562.189571759256", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1010401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1010401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1010401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1010401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-2801]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2801]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2801]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2801]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-2801]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-2801]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-1C01]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1C01]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1C01]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-1C01]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-1C01]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-1C01]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1C01]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-1C01]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-3801]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3801]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3801]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-3801]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-3801]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-3801]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3801]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-3801]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-2401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-2401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-2401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-2401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-2401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-2B]mmm dd yyyy h:mm AM/PM", "\u0540\u0576\u057E 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2B]mmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2B]mmmmm dd yyyy h:mm AM/PM", "\u0540 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2B]mmmmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"},
{"43543.503206018519", "[$-2B]mmm dd yyyy h:mm AM/PM", "\u0544\u0580\u057F 19 2019 12:04 PM"},
{"43543.503206018519", "[$-2B]mmmm dd yyyy h:mm AM/PM aaa", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0584"},
{"43543.503206018519", "[$-2B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0544 19 2019 12:04 PM \u0535\u0580\u0584"},
{"43543.503206018519", "[$-2B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B"},
{"44562.189571759256", "[$-42B]mmm dd yyyy h:mm AM/PM", "\u0540\u0576\u057E 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42B]mmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42B]mmmmm dd yyyy h:mm AM/PM", "\u0540 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42B]mmmmmm dd yyyy h:mm AM/PM", "\u0540\u0578\u0582\u0576\u057E\u0561\u0580 01 2022 4:32 AM"},
{"43543.503206018519", "[$-42B]mmm dd yyyy h:mm AM/PM", "\u0544\u0580\u057F 19 2019 12:04 PM"},
{"43543.503206018519", "[$-42B]mmmm dd yyyy h:mm AM/PM aaa", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0584"},
{"43543.503206018519", "[$-42B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0544 19 2019 12:04 PM \u0535\u0580\u0584"},
{"43543.503206018519", "[$-42B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0544\u0561\u0580\u057F 19 2019 12:04 PM \u0535\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056B"},
{"44562.189571759256", "[$-4D]mmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-4D]mmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-4D]mmmmm dd yyyy h:mm AM/PM", "\u099C 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-4D]mmmmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"43543.503206018519", "[$-4D]mmm dd yyyy h:mm AM/PM", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF"},
{"43543.503206018519", "[$-4D]mmmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-4D]mmmmm dd yyyy h:mm AM/PM ddd", "\u09AE 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-4D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0"},
{"44562.189571759256", "[$-44D]mmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-44D]mmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-44D]mmmmm dd yyyy h:mm AM/PM", "\u099C 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"44562.189571759256", "[$-44D]mmmmmm dd yyyy h:mm AM/PM", "\u099C\u09BE\u09A8\u09C1\u09F1\u09BE\u09F0\u09C0 01 2022 4:32 \u09F0\u09BE\u09A4\u09BF\u09AA\u09C1"},
{"43543.503206018519", "[$-44D]mmm dd yyyy h:mm AM/PM", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF"},
{"43543.503206018519", "[$-44D]mmmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-44D]mmmmm dd yyyy h:mm AM/PM ddd", "\u09AE 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-44D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 \u0986\u09AC\u09C7\u09B2\u09BF \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09F0"},
{"44562.189571759256", "[$-742C]mmm dd yyyy h:mm AM/PM", "\u0408\u0430\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-742C]mmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-742C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-742C]mmmmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-742C]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-742C]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0427\u0430"},
{"43543.503206018519", "[$-742C]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0427\u0430"},
{"43543.503206018519", "[$-742C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B"},
{"44562.189571759256", "[$-82C]mmm dd yyyy h:mm AM/PM", "\u0408\u0430\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82C]mmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82C]mmmmmm dd yyyy h:mm AM/PM", "j\u0430\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-82C]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-82C]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0427\u0430"},
{"43543.503206018519", "[$-82C]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0427\u0430"},
{"43543.503206018519", "[$-82C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0447\u04D9\u0440\u0448\u04D9\u043D\u0431\u04D9 \u0430\u0445\u0448\u0430\u043C\u044B"},
{"44562.189571759256", "[$-2C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-2C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-2C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-2C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-2C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"},
{"44562.189571759256", "[$-782C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-782C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-782C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-782C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-782C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-782C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-782C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-782C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"},
{"44562.189571759256", "[$-42C]mmm dd yyyy h:mm AM/PM", "yan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42C]mmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42C]mmmmm dd yyyy h:mm AM/PM", "y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42C]mmmmmm dd yyyy h:mm AM/PM", "yanvar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-42C]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-42C]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-42C]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM Ç.A."},
{"43543.503206018519", "[$-42C]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM ç\u0259r\u015F\u0259nb\u0259 ax\u015Fam\u0131"},
{"43543.503206018519", "[$-45]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-45]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-45]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"},
{"43543.503206018519", "[$-845]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-845]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-845]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"},
{"43543.503206018519", "[$-445]mmm dd yyyy h:mm AM/PM aaa", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-445]mmmm dd yyyy h:mm AM/PM ddd", "\u09AE\u09BE\u09B0\u09CD\u099A 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2."},
{"43543.503206018519", "[$-445]mmmmm dd yyyy h:mm AM/PM dddd", "\u09AE 19 2019 12:04 PM \u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0"},
{"44562.189571759256", "[$-6D]mmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6D]mmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6D]mmmmm dd yyyy h:mm AM/PM", "\u0493 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6D]mmmmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-6D]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-6D]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0448"},
{"43543.503206018519", "[$-6D]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0428\u0448"},
{"43543.503206018519", "[$-6D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0438\u0448\u04D9\u043C\u0431\u0435"},
{"44562.189571759256", "[$-46D]mmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46D]mmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46D]mmmmm dd yyyy h:mm AM/PM", "\u0493 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46D]mmmmmm dd yyyy h:mm AM/PM", "\u0493\u0438\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-46D]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-46D]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0448"},
{"43543.503206018519", "[$-46D]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0428\u0448"},
{"43543.503206018519", "[$-46D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0428\u0438\u0448\u04D9\u043C\u0431\u0435"},
{"44562.189571759256", "[$-2D]mmm dd yyyy h:mm AM/PM", "urt. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2D]mmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2D]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2D]mmmmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"},
{"43543.503206018519", "[$-2D]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-2D]mmmm dd yyyy h:mm AM/PM aaa", "martxoa 19 2019 12:04 PM ar."},
{"43543.503206018519", "[$-2D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ar."},
{"43543.503206018519", "[$-2D]mmmmmm dd yyyy h:mm AM/PM dddd", "martxoa 19 2019 12:04 PM asteartea"},
{"44562.189571759256", "[$-42D]mmm dd yyyy h:mm AM/PM", "urt. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42D]mmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42D]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42D]mmmmmm dd yyyy h:mm AM/PM", "urtarrila 01 2022 4:32 AM"},
{"43543.503206018519", "[$-42D]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-42D]mmmm dd yyyy h:mm AM/PM aaa", "martxoa 19 2019 12:04 PM ar."},
{"43543.503206018519", "[$-42D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ar."},
{"43543.503206018519", "[$-42D]mmmmmm dd yyyy h:mm AM/PM dddd", "martxoa 19 2019 12:04 PM asteartea"},
{"44562.189571759256", "[$-23]mmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437 01 2022 4:32 AM"},
{"44562.189571759256", "[$-23]mmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-23]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"},
{"44562.189571759256", "[$-23]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"},
{"43543.503206018519", "[$-23]mmm dd yyyy h:mm AM/PM", "\u0441\u0430\u043A 19 2019 12:04 PM"},
{"43543.503206018519", "[$-23]mmmm dd yyyy h:mm AM/PM aaa", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442"},
{"43543.503206018519", "[$-23]mmmmm dd yyyy h:mm AM/PM ddd", "\u0441 19 2019 12:04 PM \u0430\u045E\u0442"},
{"43543.503206018519", "[$-23]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-423]mmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437 01 2022 4:32 AM"},
{"44562.189571759256", "[$-423]mmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-423]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"},
{"44562.189571759256", "[$-423]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0442\u0443\u0434\u0437\u0435\u043D\u044C 01 2022 4:32 AM"},
{"43543.503206018519", "[$-423]mmm dd yyyy h:mm AM/PM", "\u0441\u0430\u043A 19 2019 12:04 PM"},
{"43543.503206018519", "[$-423]mmmm dd yyyy h:mm AM/PM aaa", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442"},
{"43543.503206018519", "[$-423]mmmmm dd yyyy h:mm AM/PM ddd", "\u0441 19 2019 12:04 PM \u0430\u045E\u0442"},
{"43543.503206018519", "[$-423]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0441\u0430\u043A\u0430\u0432\u0456\u043A 19 2019 12:04 PM \u0430\u045E\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-641A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-641A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-641A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-641A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-641A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-641A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-641A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-641A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-201A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-201A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-201A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-201A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-201A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-201A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-201A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-201A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-681A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-681A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-681A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-681A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-681A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-681A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-681A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-681A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"},
{"44562.189571759256", "[$-781A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-781A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-781A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-781A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-781A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-781A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-781A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-781A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"},
{"44562.189571759256", "[$-141A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-141A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-141A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-141A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-141A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-141A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-141A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto"},
{"43543.503206018519", "[$-141A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"},
{"44562.189571759256", "[$-7E]mmm dd yyyy h:mm AM/PM", "Gen. 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-7E]mmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-7E]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-7E]mmmmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."},
{"43543.503206018519", "[$-7E]mmm dd yyyy h:mm AM/PM", "Meur. 19 2019 12:04 G.M."},
{"43543.503206018519", "[$-7E]mmmm dd yyyy h:mm AM/PM aaa", "Meurzh 19 2019 12:04 G.M. Meu."},
{"43543.503206018519", "[$-7E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 G.M. Meu."},
{"43543.503206018519", "[$-7E]mmmmmm dd yyyy h:mm AM/PM dddd", "Meurzh 19 2019 12:04 G.M. Meurzh"},
{"44562.189571759256", "[$-47E]mmm dd yyyy h:mm AM/PM", "Gen. 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-47E]mmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-47E]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-47E]mmmmmm dd yyyy h:mm AM/PM", "Genver 01 2022 4:32 A.M."},
{"43543.503206018519", "[$-47E]mmm dd yyyy h:mm AM/PM", "Meur. 19 2019 12:04 G.M."},
{"43543.503206018519", "[$-47E]mmmm dd yyyy h:mm AM/PM aaa", "Meurzh 19 2019 12:04 G.M. Meu."},
{"43543.503206018519", "[$-47E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 G.M. Meu."},
{"43543.503206018519", "[$-47E]mmmmmm dd yyyy h:mm AM/PM dddd", "Meurzh 19 2019 12:04 G.M. Meurzh"},
{"44562.189571759256", "[$-2]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-2]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"},
{"43543.503206018519", "[$-2]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-2]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442"},
{"43543.503206018519", "[$-2]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0432\u0442"},
{"43543.503206018519", "[$-2]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"44562.189571759256", "[$-402]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443 01 2022 4:32 AM"},
{"44562.189571759256", "[$-402]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"},
{"44562.189571759256", "[$-402]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-402]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 AM"},
{"43543.503206018519", "[$-402]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-402]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442"},
{"43543.503206018519", "[$-402]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0432\u0442"},
{"43543.503206018519", "[$-402]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"44562.189571759256", "[$-55]mmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-55]mmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-55]mmmmm dd yyyy h:mm AM/PM", "\u1007 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-55]mmmmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"43543.503206018519", "[$-55]mmm dd yyyy h:mm AM/PM", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031"},
{"43543.503206018519", "[$-55]mmmm dd yyyy h:mm AM/PM aaa", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"43543.503206018519", "[$-55]mmmmm dd yyyy h:mm AM/PM ddd", "\u1019 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"43543.503206018519", "[$-55]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"44562.189571759256", "[$-455]mmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-455]mmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-455]mmmmm dd yyyy h:mm AM/PM", "\u1007 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"44562.189571759256", "[$-455]mmmmmm dd yyyy h:mm AM/PM", "\u1007\u1014\u103A\u1014\u101D\u102B\u101B\u102E 01 2022 4:32 \u1014\u1036\u1014\u1000\u103A"},
{"43543.503206018519", "[$-455]mmm dd yyyy h:mm AM/PM", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031"},
{"43543.503206018519", "[$-455]mmmm dd yyyy h:mm AM/PM aaa", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"43543.503206018519", "[$-455]mmmmm dd yyyy h:mm AM/PM ddd", "\u1019 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"43543.503206018519", "[$-455]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1019\u1010\u103A 19 2019 12:04 \u100A\u1014\u1031 \u1021\u1004\u103A\u1039\u1002\u102B"},
{"44562.189571759256", "[$-3]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-3]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-3]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-3]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-3]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-3]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-3]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-3]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"},
{"44562.189571759256", "[$-403]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-403]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-403]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-403]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-403]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-403]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-403]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-403]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"},
{"44562.189571759256", "[$-45F]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-45F]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-45F]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
{"44562.189571759256", "[$-45F]mmmmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
{"43543.503206018519", "[$-45F]mmm dd yyyy h:mm AM/PM", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645"},
{"43543.503206018519", "[$-45F]mmmm dd yyyy h:mm AM/PM aaa", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-45F]mmmmm dd yyyy h:mm AM/PM ddd", "\u0622 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"43543.503206018519", "[$-45F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0622\u0630\u0627\u0631 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
{"44562.189571759256", "[$-92]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-92]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-92]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-92]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"43543.503206018519", "[$-92]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"},
{"43543.503206018519", "[$-92]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-92]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-92]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"44562.189571759256", "[$-7C92]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-7C92]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-7C92]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-7C92]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"43543.503206018519", "[$-7C92]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"},
{"43543.503206018519", "[$-7C92]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-7C92]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-7C92]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"44562.189571759256", "[$-492]mmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-492]mmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-492]mmmmm dd yyyy h:mm AM/PM", "\u06A9 01 2022 4:32 \u067E.\u0646"},
{"44562.189571759256", "[$-492]mmmmmm dd yyyy h:mm AM/PM", "\u06A9\u0627\u0646\u0648\u0648\u0646\u06CC \u062F\u0648\u0648\u06D5\u0645 01 2022 4:32 \u067E.\u0646"},
{"43543.503206018519", "[$-492]mmm dd yyyy h:mm AM/PM", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646"},
{"43543.503206018519", "[$-492]mmmm dd yyyy h:mm AM/PM aaa", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-492]mmmmm dd yyyy h:mm AM/PM ddd", "\u0626 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"43543.503206018519", "[$-492]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0626\u0627\u0632\u0627\u0631 19 2019 12:04 \u062F.\u0646 \u0633\u06CE\u0634\u06D5\u0645\u0645\u06D5"},
{"44562.189571759256", "[$-5C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"43543.503206018519", "[$-5C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"},
{"43543.503206018519", "[$-5C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-5C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-5C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"},
{"44562.189571759256", "[$-7C5C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C5C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C5C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-7C5C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-7C5C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"},
{"44562.189571759256", "[$-45C]mmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45C]mmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45C]mmmmm dd yyyy h:mm AM/PM", "\u13A4 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45C]mmmmmm dd yyyy h:mm AM/PM", "\u13A4\u13C3\u13B8\u13D4\u13C5 01 2022 4:32 AM"},
{"43543.503206018519", "[$-45C]mmm dd yyyy h:mm AM/PM", "\u13A0\u13C5\u13F1 19 2019 12:04 PM"},
{"43543.503206018519", "[$-45C]mmmm dd yyyy h:mm AM/PM aaa", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-45C]mmmmm dd yyyy h:mm AM/PM ddd", "\u13A0 19 2019 12:04 PM \u13D4\u13B5\u13C1"},
{"43543.503206018519", "[$-45C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u13A0\u13C5\u13F1 19 2019 12:04 PM \u13D4\u13B5\u13C1\u13A2\u13A6"},
{"43543.503206018519", "[$-4]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-4]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-4]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-7804]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-7804]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-7804]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-804]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 周二"},
{"43543.503206018519", "[$-804]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 周二"},
{"43543.503206018519", "[$-804]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-1004]mmm dd yyyy h:mm AM/PM aaa", "三月 19 2019 12:04 下午 周二"},
{"43543.503206018519", "[$-1004]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 周二"},
{"43543.503206018519", "[$-1004]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-7C04]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-7C04]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-7C04]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-C04]mmm dd yyyy h:mm AM/PM aaa", "三月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-C04]mmmm dd yyyy h:mm AM/PM ddd", "三月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-C04]mmmmm dd yyyy h:mm AM/PM dddd", "三 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-1404]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-1404]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-1404]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"},
{"43543.503206018519", "[$-404]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-404]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 下午 週二"},
{"43543.503206018519", "[$-404]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 下午 星期二"},
{"44562.189571759256", "[$-83]mmm dd yyyy h:mm AM/PM", "ghje 01 2022 4:32 AM"},
{"44593.189571759256", "[$-83]mmm dd yyyy h:mm AM/PM", "ferr 01 2022 4:32 AM"},
{"44621.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "marz 01 2022 4:32 AM"},
{"44652.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "apri 01 2022 4:32 AM"},
{"44682.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "magh 01 2022 4:32 AM"},
{"44713.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "ghju 01 2022 4:32 AM"},
{"44743.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "lugl 01 2022 4:32 AM"},
{"44774.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "aost 01 2022 4:32 AM"},
{"44805.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "sett 01 2022 4:32 AM"},
{"44835.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "otto 01 2022 4:32 AM"},
{"44866.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "nuve 01 2022 4:32 AM"},
{"44896.18957170139", "[$-83]mmm dd yyyy h:mm AM/PM", "dice 01 2022 4:32 AM"},
{"44562.189571759256", "[$-83]mmmm dd yyyy h:mm AM/PM", "ghjennaghju 01 2022 4:32 AM"},
{"44593.189571759256", "[$-83]mmmm dd yyyy h:mm AM/PM", "ferraghju 01 2022 4:32 AM"},
{"44621.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "marzu 01 2022 4:32 AM"},
{"44652.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "aprile 01 2022 4:32 AM"},
{"44682.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "maghju 01 2022 4:32 AM"},
{"44713.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "ghjunghju 01 2022 4:32 AM"},
{"44743.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "lugliu 01 2022 4:32 AM"},
{"44774.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "aostu 01 2022 4:32 AM"},
{"44805.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "settembre 01 2022 4:32 AM"},
{"44835.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "ottobre 01 2022 4:32 AM"},
{"44866.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "nuvembre 01 2022 4:32 AM"},
{"44896.18957170139", "[$-83]mmmm dd yyyy h:mm AM/PM", "dicembre 01 2022 4:32 AM"},
{"44562.189571759256", "[$-83]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44593.189571759256", "[$-83]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44743.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"},
{"44774.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM sab."},
{"44866.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM mar."},
{"44896.18957170139", "[$-83]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM ghjovi"},
{"44562.189571759256", "[$-483]mmm dd yyyy h:mm AM/PM", "ghje 01 2022 4:32 AM"},
{"44593.189571759256", "[$-483]mmm dd yyyy h:mm AM/PM", "ferr 01 2022 4:32 AM"},
{"44621.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "marz 01 2022 4:32 AM"},
{"44652.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "apri 01 2022 4:32 AM"},
{"44682.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "magh 01 2022 4:32 AM"},
{"44713.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "ghju 01 2022 4:32 AM"},
{"44743.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "lugl 01 2022 4:32 AM"},
{"44774.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "aost 01 2022 4:32 AM"},
{"44805.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "sett 01 2022 4:32 AM"},
{"44835.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "otto 01 2022 4:32 AM"},
{"44866.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "nuve 01 2022 4:32 AM"},
{"44896.18957170139", "[$-483]mmm dd yyyy h:mm AM/PM", "dice 01 2022 4:32 AM"},
{"44562.189571759256", "[$-483]mmmm dd yyyy h:mm AM/PM", "ghjennaghju 01 2022 4:32 AM"},
{"44593.189571759256", "[$-483]mmmm dd yyyy h:mm AM/PM", "ferraghju 01 2022 4:32 AM"},
{"44621.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "marzu 01 2022 4:32 AM"},
{"44652.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "aprile 01 2022 4:32 AM"},
{"44682.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "maghju 01 2022 4:32 AM"},
{"44713.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "ghjunghju 01 2022 4:32 AM"},
{"44743.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "lugliu 01 2022 4:32 AM"},
{"44774.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "aostu 01 2022 4:32 AM"},
{"44805.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "settembre 01 2022 4:32 AM"},
{"44835.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "ottobre 01 2022 4:32 AM"},
{"44866.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "nuvembre 01 2022 4:32 AM"},
{"44896.18957170139", "[$-483]mmmm dd yyyy h:mm AM/PM", "dicembre 01 2022 4:32 AM"},
{"44562.189571759256", "[$-483]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44593.189571759256", "[$-483]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44743.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"},
{"44774.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM sab."},
{"44866.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM mar."},
{"44896.18957170139", "[$-483]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM ghjovi"},
{"44562.189571759256", "[$-1A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1A]mmm dd yyyy h:mm AM/PM", "vlj 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"},
{"44866.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"},
{"44896.18957170139", "[$-1A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"},
{"44866.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"},
{"44896.18957170139", "[$-1A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"},
{"44866.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"},
{"44896.18957170139", "[$-1A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"},
{"44562.189571759256", "[$-41A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41A]mmm dd yyyy h:mm AM/PM", "vlj 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"},
{"44866.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"},
{"44896.18957170139", "[$-41A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"},
{"44866.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"},
{"44896.18957170139", "[$-41A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"},
{"44866.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"},
{"44896.18957170139", "[$-41A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"},
{"44562.189571759256", "[$-101A]mmm dd yyyy h:mm AM/PM", "sij 01 2022 4:32 AM"},
{"44593.189571759256", "[$-101A]mmm dd yyyy h:mm AM/PM", "velj 01 2022 4:32 AM"},
{"44621.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "ožu 01 2022 4:32 AM"},
{"44652.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "tra 01 2022 4:32 AM"},
{"44682.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "svi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "lip 01 2022 4:32 AM"},
{"44743.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "srp 01 2022 4:32 AM"},
{"44774.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "kol 01 2022 4:32 AM"},
{"44805.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "ruj 01 2022 4:32 AM"},
{"44835.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "lis 01 2022 4:32 AM"},
{"44866.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "stu 01 2022 4:32 AM"},
{"44896.18957170139", "[$-101A]mmm dd yyyy h:mm AM/PM", "pro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-101A]mmmm dd yyyy h:mm AM/PM", "siječanj 01 2022 4:32 AM"},
{"44593.189571759256", "[$-101A]mmmm dd yyyy h:mm AM/PM", "veljača 01 2022 4:32 AM"},
{"44621.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "ožujak 01 2022 4:32 AM"},
{"44652.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "travanj 01 2022 4:32 AM"},
{"44682.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "svibanj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "lipanj 01 2022 4:32 AM"},
{"44743.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "srpanj 01 2022 4:32 AM"},
{"44774.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "kolovoz 01 2022 4:32 AM"},
{"44805.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "rujan 01 2022 4:32 AM"},
{"44835.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 AM"},
{"44866.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "studeni 01 2022 4:32 AM"},
{"44896.18957170139", "[$-101A]mmmm dd yyyy h:mm AM/PM", "prosinac 01 2022 4:32 AM"},
{"44562.189571759256", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44593.189571759256", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "v 01 2022 4:32 AM"},
{"44621.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44652.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44682.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44713.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 AM"},
{"44743.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44774.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"},
{"44805.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM", "r 01 2022 4:32 AM"},
{"44835.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM aaa", "l 01 2022 4:32 AM sub"},
{"44866.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM ddd", "s 01 2022 4:32 AM uto"},
{"44896.18957170139", "[$-101A]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 AM četvrtak"},
{"44562.189571759256", "[$-5]mmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 dop."},
{"44593.189571759256", "[$-5]mmm dd yyyy h:mm AM/PM", "II 01 2022 4:32 dop."},
{"44621.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "III 01 2022 4:32 dop."},
{"44652.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "IV 01 2022 4:32 dop."},
{"44682.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "V 01 2022 4:32 dop."},
{"44713.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VI 01 2022 4:32 dop."},
{"44743.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VII 01 2022 4:32 dop."},
{"44774.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "VIII 01 2022 4:32 dop."},
{"44805.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "IX 01 2022 4:32 dop."},
{"44835.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 dop."},
{"44866.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "XI 01 2022 4:32 dop."},
{"44896.18957170139", "[$-5]mmm dd yyyy h:mm AM/PM", "XII 01 2022 4:32 dop."},
{"44562.189571759256", "[$-5]mmmm dd yyyy h:mm AM/PM", "leden 01 2022 4:32 dop."},
{"44593.189571759256", "[$-5]mmmm dd yyyy h:mm AM/PM", "únor 01 2022 4:32 dop."},
{"44621.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "březen 01 2022 4:32 dop."},
{"44652.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "duben 01 2022 4:32 dop."},
{"44682.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "květen 01 2022 4:32 dop."},
{"44713.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "červen 01 2022 4:32 dop."},
{"44743.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "červenec 01 2022 4:32 dop."},
{"44774.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "srpen 01 2022 4:32 dop."},
{"44805.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "září 01 2022 4:32 dop."},
{"44835.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "říjen 01 2022 4:32 dop."},
{"44866.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 dop."},
{"44896.18957170139", "[$-5]mmmm dd yyyy h:mm AM/PM", "prosinec 01 2022 4:32 dop."},
{"44562.189571759256", "[$-5]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 dop."},
{"44593.189571759256", "[$-5]mmmmm dd yyyy h:mm AM/PM", "ú 01 2022 4:32 dop."},
{"44621.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "b 01 2022 4:32 dop."},
{"44652.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "d 01 2022 4:32 dop."},
{"44682.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 dop."},
{"44713.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."},
{"44743.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."},
{"44774.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 dop."},
{"44805.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM", "z 01 2022 4:32 dop."},
{"44835.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM aaa", "ř 01 2022 4:32 dop. so"},
{"44866.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM ddd", "l 01 2022 4:32 dop. út"},
{"44896.18957170139", "[$-5]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 dop. čtvrtek"},
{"44562.189571759256", "[$-405]mmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 dop."},
{"44593.189571759256", "[$-405]mmm dd yyyy h:mm AM/PM", "II 01 2022 4:32 dop."},
{"44621.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "III 01 2022 4:32 dop."},
{"44652.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "IV 01 2022 4:32 dop."},
{"44682.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "V 01 2022 4:32 dop."},
{"44713.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VI 01 2022 4:32 dop."},
{"44743.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VII 01 2022 4:32 dop."},
{"44774.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "VIII 01 2022 4:32 dop."},
{"44805.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "IX 01 2022 4:32 dop."},
{"44835.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 dop."},
{"44866.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "XI 01 2022 4:32 dop."},
{"44896.18957170139", "[$-405]mmm dd yyyy h:mm AM/PM", "XII 01 2022 4:32 dop."},
{"44562.189571759256", "[$-405]mmmm dd yyyy h:mm AM/PM", "leden 01 2022 4:32 dop."},
{"44593.189571759256", "[$-405]mmmm dd yyyy h:mm AM/PM", "únor 01 2022 4:32 dop."},
{"44621.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "březen 01 2022 4:32 dop."},
{"44652.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "duben 01 2022 4:32 dop."},
{"44682.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "květen 01 2022 4:32 dop."},
{"44713.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "červen 01 2022 4:32 dop."},
{"44743.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "červenec 01 2022 4:32 dop."},
{"44774.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "srpen 01 2022 4:32 dop."},
{"44805.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "září 01 2022 4:32 dop."},
{"44835.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "říjen 01 2022 4:32 dop."},
{"44866.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "listopad 01 2022 4:32 dop."},
{"44896.18957170139", "[$-405]mmmm dd yyyy h:mm AM/PM", "prosinec 01 2022 4:32 dop."},
{"44562.189571759256", "[$-405]mmmmm dd yyyy h:mm AM/PM", "l 01 2022 4:32 dop."},
{"44593.189571759256", "[$-405]mmmmm dd yyyy h:mm AM/PM", "ú 01 2022 4:32 dop."},
{"44621.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "b 01 2022 4:32 dop."},
{"44652.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "d 01 2022 4:32 dop."},
{"44682.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 dop."},
{"44713.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."},
{"44743.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "č 01 2022 4:32 dop."},
{"44774.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 dop."},
{"44805.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM", "z 01 2022 4:32 dop."},
{"44835.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM aaa", "ř 01 2022 4:32 dop. so"},
{"44866.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM ddd", "l 01 2022 4:32 dop. út"},
{"44896.18957170139", "[$-405]mmmmm dd yyyy h:mm AM/PM dddd", "p 01 2022 4:32 dop. čtvrtek"},
{"44562.189571759256", "[$-6]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-6]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "mar 01 2022 4:32 AM"},
{"44652.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"},
{"44682.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"},
{"44805.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-6]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44593.189571759256", "[$-6]mmmm dd yyyy h:mm AM/PM", "februar 01 2022 4:32 AM"},
{"44621.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "marts 01 2022 4:32 AM"},
{"44652.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"},
{"44682.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"},
{"44774.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "august 01 2022 4:32 AM"},
{"44805.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"},
{"44835.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"},
{"44866.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"},
{"44896.18957170139", "[$-6]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44593.189571759256", "[$-6]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44743.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44774.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM lø"},
{"44866.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM ti"},
{"44896.18957170139", "[$-6]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM torsdag"},
{"44562.189571759256", "[$-406]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-406]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "mar 01 2022 4:32 AM"},
{"44652.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"},
{"44682.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"},
{"44805.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-406]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"},
{"44562.189571759256", "[$-406]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44593.189571759256", "[$-406]mmmm dd yyyy h:mm AM/PM", "februar 01 2022 4:32 AM"},
{"44621.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "marts 01 2022 4:32 AM"},
{"44652.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"},
{"44682.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "maj 01 2022 4:32 AM"},
{"44713.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"},
{"44774.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "august 01 2022 4:32 AM"},
{"44805.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"},
{"44835.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"},
{"44866.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"},
{"44896.18957170139", "[$-406]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"},
{"44562.189571759256", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44593.189571759256", "[$-406]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44743.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44774.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM lø"},
{"44866.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM ti"},
{"44896.18957170139", "[$-406]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM torsdag"},
{"44562.189571759256", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062C\u062F\u06CC 01 2022 4:32 \u0642.\u0638"},
{"44593.189571759256", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062F\u0644\u0648 01 2022 4:32 \u0642.\u0638"},
{"44621.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062D\u0648\u062A 01 2022 4:32 \u0642.\u0638"},
{"44652.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062D\u0645\u0644 01 2022 4:32 \u0642.\u0638"},
{"44682.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062B\u0648\u0631 01 2022 4:32 \u0642.\u0638"},
{"44713.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0632\u0627 01 2022 4:32 \u0642.\u0638"},
{"44743.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0633\u0631\u0637\u0627\u0646 01 2022 4:32 \u0642.\u0638"},
{"44774.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0627\u0633\u062F 01 2022 4:32 \u0642.\u0638"},
{"44805.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0633\u0646\u0628\u0644\u0647 01 2022 4:32 \u0642.\u0638"},
{"44835.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0645\u06CC\u0632\u0627\u0646 01 2022 4:32 \u0642.\u0638"},
{"44866.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0639\u0642\u0631\u0628 01 2022 4:32 \u0642.\u0638"},
{"44896.18957170139", "[$-8C]mmm dd yyyy h:mm AM/PM", "\u0642\u0648\u0633 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44593.189571759256", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0641\u0648\u0631\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44621.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 01 2022 4:32 \u0642.\u0638"},
{"44652.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0622\u0648\u0631\u064A\u0644 01 2022 4:32 \u0642.\u0638"},
{"44682.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0647 01 2022 4:32 \u0642.\u0638"},
{"44713.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0648\u0626\u0646 01 2022 4:32 \u0642.\u0638"},
{"44743.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0698\u0648\u0626\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44774.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0627\u0648\u062A 01 2022 4:32 \u0642.\u0638"},
{"44805.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0633\u067E\u062A\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"},
{"44835.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0627\u064F\u0643\u062A\u0628\u0631 01 2022 4:32 \u0642.\u0638"},
{"44866.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u0646\u0648\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"},
{"44896.18957170139", "[$-8C]mmmm dd yyyy h:mm AM/PM", "\u062F\u0633\u0627\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"},
{"44593.189571759256", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0641 01 2022 4:32 \u0642.\u0638"},
{"44621.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638"},
{"44652.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0622 01 2022 4:32 \u0642.\u0638"},
{"44682.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638"},
{"44713.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"},
{"44743.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"},
{"44774.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638"},
{"44805.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM", "\u0633 01 2022 4:32 \u0642.\u0638"},
{"44835.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM aaa", "\u0627 01 2022 4:32 \u0642.\u0638 \u0634\u0646\u0628\u0647"},
{"44866.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0646 01 2022 4:32 \u0642.\u0638 \u0633\u0647\u200C \u0634\u0646\u0628\u0647"},
{"44896.18957170139", "[$-8C]mmmmm dd yyyy h:mm AM/PM dddd", "\u062F 01 2022 4:32 \u0642.\u0638 \u067E\u0646\u062C\u0634\u0646\u0628\u0647"},
{"44562.189571759256", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062C\u062F\u06CC 01 2022 4:32 \u0642.\u0638."},
{"44593.189571759256", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062F\u0644\u0648 01 2022 4:32 \u0642.\u0638."},
{"44621.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062D\u0648\u062A 01 2022 4:32 \u0642.\u0638."},
{"44652.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062D\u0645\u0644 01 2022 4:32 \u0642.\u0638."},
{"44682.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062B\u0648\u0631 01 2022 4:32 \u0642.\u0638."},
{"44713.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0632\u0627 01 2022 4:32 \u0642.\u0638."},
{"44743.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0633\u0631\u0637\u0627\u0646 01 2022 4:32 \u0642.\u0638."},
{"44774.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0627\u0633\u062F 01 2022 4:32 \u0642.\u0638."},
{"44805.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0633\u0646\u0628\u0644\u0647 01 2022 4:32 \u0642.\u0638."},
{"44835.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0645\u06CC\u0632\u0627\u0646 01 2022 4:32 \u0642.\u0638."},
{"44866.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0639\u0642\u0631\u0628 01 2022 4:32 \u0642.\u0638."},
{"44896.18957170139", "[$-48C]mmm dd yyyy h:mm AM/PM", "\u0642\u0648\u0633 01 2022 4:32 \u0642.\u0638."},
{"44562.189571759256", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u0642.\u0638."},
{"44593.189571759256", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0641\u0628\u0631\u0648\u0631\u06CC 01 2022 4:32 \u0642.\u0638."},
{"44621.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 01 2022 4:32 \u0642.\u0638."},
{"44652.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u067E\u0631\u06CC\u0644 01 2022 4:32 \u0642.\u0638."},
{"44682.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0645\u06CC 01 2022 4:32 \u0642.\u0638."},
{"44713.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0646 01 2022 4:32 \u0642.\u0638."},
{"44743.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062C\u0648\u0644\u0627\u06CC 01 2022 4:32 \u0642.\u0638."},
{"44774.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u06AF\u0633\u062A 01 2022 4:32 \u0642.\u0638."},
{"44805.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0633\u067E\u062A\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."},
{"44835.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0627\u06A9\u062A\u0648\u0628\u0631 01 2022 4:32 \u0642.\u0638."},
{"44866.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u0646\u0648\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."},
{"44896.18957170139", "[$-48C]mmmm dd yyyy h:mm AM/PM", "\u062F\u0633\u0645\u0628\u0631 01 2022 4:32 \u0642.\u0638."},
{"44562.189571759256", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."},
{"44593.189571759256", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0641 01 2022 4:32 \u0642.\u0638."},
{"44621.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638."},
{"44652.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638."},
{"44682.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0645 01 2022 4:32 \u0642.\u0638."},
{"44713.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."},
{"44743.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u0642.\u0638."},
{"44774.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0627 01 2022 4:32 \u0642.\u0638."},
{"44805.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM", "\u0633 01 2022 4:32 \u0642.\u0638."},
{"44835.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM aaa", "\u0627 01 2022 4:32 \u0642.\u0638. \u0634\u0646\u0628\u0647"},
{"44866.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0646 01 2022 4:32 \u0642.\u0638. \u0633\u0647\u200C \u0634\u0646\u0628\u0647"},
{"44896.18957170139", "[$-48C]mmmmm dd yyyy h:mm AM/PM dddd", "\u062F 01 2022 4:32 \u0642.\u0638. \u067E\u0646\u062C\u0634\u0646\u0628\u0647"},
{"44562.189571759256", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-65]mmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44866.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44896.18957170139", "[$-65]mmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44562.189571759256", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44866.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44896.18957170139", "[$-65]mmmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44562.189571759256", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u078A 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM", "\u0790 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM aaa", "\u0787 01 2022 4:32 \u0789\u0786 \u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA"},
{"44866.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM ddd", "\u0782 01 2022 4:32 \u0789\u0786 \u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6"},
{"44896.18957170139", "[$-65]mmmmm dd yyyy h:mm AM/PM dddd", "\u0791 01 2022 4:32 \u0789\u0786 \u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8"},
{"44562.189571759256", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-465]mmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44866.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44896.18957170139", "[$-465]mmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44562.189571759256", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07A6\u0782\u07A6\u0788\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u078A\u07AC\u0784\u07B0\u0783\u07AA\u0787\u07A6\u0783\u07A9 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0789\u07A7\u0783\u0797\u07B0 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AD\u0795\u07B0\u0783\u07A8\u078D\u07B0 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0789\u07AC\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AB\u0782\u07B0 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0796\u07AA\u078D\u07A6\u0787\u07A8 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u078E\u07A6\u0790\u07B0\u0793\u07B0 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0790\u07AC\u0795\u07B0\u0793\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0787\u07AE\u0786\u07B0\u0793\u07AF\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44866.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0782\u07AE\u0788\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44896.18957170139", "[$-465]mmmm dd yyyy h:mm AM/PM", "\u0791\u07A8\u0790\u07AC\u0789\u07B0\u0784\u07A6\u0783 01 2022 4:32 \u0789\u0786"},
{"44562.189571759256", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44593.189571759256", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u078A 01 2022 4:32 \u0789\u0786"},
{"44621.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"},
{"44652.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"},
{"44682.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0789 01 2022 4:32 \u0789\u0786"},
{"44713.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44743.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0796 01 2022 4:32 \u0789\u0786"},
{"44774.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0787 01 2022 4:32 \u0789\u0786"},
{"44805.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM", "\u0790 01 2022 4:32 \u0789\u0786"},
{"44835.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM aaa", "\u0787 01 2022 4:32 \u0789\u0786 \u0780\u07AE\u0782\u07A8\u0780\u07A8\u0783\u07AA"},
{"44866.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM ddd", "\u0782 01 2022 4:32 \u0789\u0786 \u0787\u07A6\u0782\u07B0\u078E\u07A7\u0783\u07A6"},
{"44896.18957170139", "[$-465]mmmmm dd yyyy h:mm AM/PM dddd", "\u0791 01 2022 4:32 \u0789\u0786 \u0784\u07AA\u0783\u07A7\u0790\u07B0\u078A\u07A6\u078C\u07A8"},
{"44562.189571759256", "[$-13]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-13]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"},
{"44652.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"},
{"44682.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"},
{"44805.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-13]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"},
{"44562.189571759256", "[$-13]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-13]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"},
{"44652.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"},
{"44682.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"},
{"44774.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"},
{"44805.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"},
{"44835.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"},
{"44866.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"},
{"44896.18957170139", "[$-13]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"},
{"44562.189571759256", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44593.189571759256", "[$-13]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44743.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44774.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"},
{"44866.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"},
{"44896.18957170139", "[$-13]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"},
{"44562.189571759256", "[$-813]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-813]mmm dd yyyy h:mm AM/PM", "feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"},
{"44652.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"},
{"44682.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"},
{"44805.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-813]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"},
{"44562.189571759256", "[$-813]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-813]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"},
{"44652.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"},
{"44682.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"},
{"44774.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"},
{"44805.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"},
{"44835.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"},
{"44866.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"},
{"44896.18957170139", "[$-813]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"},
{"44562.189571759256", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44593.189571759256", "[$-813]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44743.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44774.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"},
{"44866.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"},
{"44896.18957170139", "[$-813]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"},
{"44621.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "mrt 01 2022 4:32 AM"},
{"44652.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "apr 01 2022 4:32 AM"},
{"44682.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "aug 01 2022 4:32 AM"},
{"44805.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-413]mmm dd yyyy h:mm AM/PM", "dec 01 2022 4:32 AM"},
{"44562.189571759256", "[$-413]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-413]mmmm dd yyyy h:mm AM/PM", "februari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "maart 01 2022 4:32 AM"},
{"44652.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "april 01 2022 4:32 AM"},
{"44682.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "mei 01 2022 4:32 AM"},
{"44713.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "juli 01 2022 4:32 AM"},
{"44774.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "augustus 01 2022 4:32 AM"},
{"44805.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "september 01 2022 4:32 AM"},
{"44835.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "oktober 01 2022 4:32 AM"},
{"44866.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "november 01 2022 4:32 AM"},
{"44896.18957170139", "[$-413]mmmm dd yyyy h:mm AM/PM", "december 01 2022 4:32 AM"},
{"44562.189571759256", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44593.189571759256", "[$-413]mmmmm dd yyyy h:mm AM/PM", "f 01 2022 4:32 AM"},
{"44621.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44652.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44682.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "m 01 2022 4:32 AM"},
{"44713.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44743.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44774.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "a 01 2022 4:32 AM"},
{"44805.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44835.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM aaa", "o 01 2022 4:32 AM za"},
{"44866.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM ddd", "n 01 2022 4:32 AM di"},
{"44896.18957170139", "[$-413]mmmmm dd yyyy h:mm AM/PM dddd", "d 01 2022 4:32 AM donderdag"},
{"44562.189571759256", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44593.189571759256", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44621.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44652.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44682.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44713.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44743.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44774.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44805.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44835.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44866.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44896.18957170139", "[$-C51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44562.189571759256", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F44\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44593.189571759256", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44621.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44652.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44682.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44713.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44743.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44774.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44805.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44835.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44866.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44896.18957170139", "[$-C51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44562.189571759256", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44593.189571759256", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44621.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44652.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44682.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44713.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44743.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44774.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44805.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B"},
{"44835.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F49\u0F72\u0F0B"},
{"44866.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F63\u0FB7\u0F42\u0F0B"},
{"44896.18957170139", "[$-C51]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F46\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F54\u0F0B\u0F66\u0F44\u0F66\u0F0B"},
{"43543.503206018519", "[$-9]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-9]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-9]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-1000]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1000]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1000]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"},
{"43543.503206018519", "[$-c09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-c09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-c09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"},
{"43543.503206018519", "[$-2809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-1009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-2409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-3C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-4009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-1809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-1809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-1809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"},
{"43543.503206018519", "[$-2009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-4409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-1409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-3409]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3409]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3409]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-4809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-1C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-1C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-2C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-2C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-4C09]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4C09]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-4C09]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-809]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-809]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 pm Tue"},
{"43543.503206018519", "[$-809]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 pm Tuesday"},
{"43543.503206018519", "[$-3009]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3009]mmmm dd yyyy h:mm AM/PM ddd", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-3009]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Tuesday"},
{"43543.503206018519", "[$-25]mmm dd yyyy h:mm AM/PM aaa", "märts 19 2019 12:04 PM T"},
{"43543.503206018519", "[$-25]mmmm dd yyyy h:mm AM/PM ddd", "märts 19 2019 12:04 PM T"},
{"43543.503206018519", "[$-25]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM teisipäev"},
{"43543.503206018519", "[$-425]mmm dd yyyy h:mm AM/PM aaa", "märts 19 2019 12:04 PM T"},
{"43543.503206018519", "[$-425]mmmm dd yyyy h:mm AM/PM ddd", "märts 19 2019 12:04 PM T"},
{"43543.503206018519", "[$-425]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM teisipäev"},
{"43543.503206018519", "[$-38]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 um sein. týs."},
{"43543.503206018519", "[$-38]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 um sein. týs."},
{"43543.503206018519", "[$-38]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 um sein. týsdagur"},
{"43543.503206018519", "[$-438]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 um sein. týs."},
{"43543.503206018519", "[$-438]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 um sein. týs."},
{"43543.503206018519", "[$-438]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 um sein. týsdagur"},
{"43543.503206018519", "[$-64]mmm dd yyyy h:mm AM/PM aaa", "Mar 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-64]mmmm dd yyyy h:mm AM/PM ddd", "Marso 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-64]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 PM Martes"},
{"43543.503206018519", "[$-464]mmm dd yyyy h:mm AM/PM ddd", "Mar 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-464]mmmm dd yyyy h:mm AM/PM ddd", "Marso 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-464]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 PM Martes"},
{"43543.503206018519", "[$-B]mmm dd yyyy h:mm AM/PM aaa", "maalis 19 2019 12:04 ip. ti"},
{"43543.503206018519", "[$-B]mmmm dd yyyy h:mm AM/PM ddd", "maaliskuu 19 2019 12:04 ip. ti"},
{"43543.503206018519", "[$-B]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 ip. tiistai"},
{"43543.503206018519", "[$-40B]mmm dd yyyy h:mm AM/PM aaa", "maalis 19 2019 12:04 ip. ti"},
{"43543.503206018519", "[$-40B]mmmm dd yyyy h:mm AM/PM ddd", "maaliskuu 19 2019 12:04 ip. ti"},
{"43543.503206018519", "[$-40B]mmmmm dd yyyy h:mm AM/PM dddd", "03 19 2019 12:04 ip. tiistai"},
{"44562.189571759256", "[$-C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-80C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-80C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-80C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-80C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-80C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-80C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-2c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 mat."},
{"44562.189571759256", "[$-2c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 mat."},
{"44562.189571759256", "[$-2c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 mat."},
{"43543.503206018519", "[$-2c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 soir mar."},
{"43543.503206018519", "[$-2c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 soir mar."},
{"43543.503206018519", "[$-2c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 soir mardi"},
{"44562.189571759256", "[$-c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-1C0C]mmm dd yyyy h:mm AM/PM", "Janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C0C]mmmm dd yyyy h:mm AM/PM", "Janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C0C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"43543.503206018519", "[$-1C0C]mmm dd yyyy h:mm AM/PM aaa", "Mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-1C0C]mmmm dd yyyy h:mm AM/PM ddd", "Mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-1C0C]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-240C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-240C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-240C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-240C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-240C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-240C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-300C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-300C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-300C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-300C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-300C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-300C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-40C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-40C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-40C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-40C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-40C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-40C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-3c0C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3c0C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3c0C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-3c0C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-3c0C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-3c0C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-140C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-140C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-140C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-140C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-140C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-140C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-340C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-340C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-340C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-340C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-340C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-340C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-380C]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-380C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-380C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-380C]mmm dd yyyy h:mm AM/PM aaa", "mar. 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-380C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-380C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-180C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-180C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-180C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-180C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-180C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-180C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-200C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-200C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-200C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-200C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-200C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-200C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-280C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-280C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-280C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-280C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-280C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-280C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-100C]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-100C]mmmm dd yyyy h:mm AM/PM", "janvier 01 2022 4:32 AM"},
{"44562.189571759256", "[$-100C]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"43543.503206018519", "[$-100C]mmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-100C]mmmm dd yyyy h:mm AM/PM ddd", "mars 19 2019 12:04 PM mar."},
{"43543.503206018519", "[$-100C]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-62]m dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"},
{"44562.189571759256", "[$-62]mm dd yyyy h:mm AM/PM", "01 01 2022 4:32 AM"},
{"44562.189571759256", "[$-62]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-62]mmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"},
{"44562.189571759256", "[$-62]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-62]mmmmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"},
{"43543.503206018519", "[$-62]m dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"},
{"43543.503206018519", "[$-62]mm dd yyyy h:mm AM/PM", "03 19 2019 12:04 PM"},
{"43543.503206018519", "[$-62]mmm dd yyyy h:mm AM/PM", "Mrt 19 2019 12:04 PM"},
{"43543.503206018519", "[$-62]mmmm dd yyyy h:mm AM/PM aaa", "Maart 19 2019 12:04 PM tii"},
{"43543.503206018519", "[$-62]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM tii"},
{"43543.503206018519", "[$-62]mmmmmm dd yyyy h:mm AM/PM dddd", "Maart 19 2019 12:04 PM tiisdei"},
{"44562.189571759256", "[$-462]m dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"},
{"44562.189571759256", "[$-462]mm dd yyyy h:mm AM/PM", "01 01 2022 4:32 AM"},
{"44562.189571759256", "[$-462]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-462]mmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"},
{"44562.189571759256", "[$-462]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-462]mmmmmm dd yyyy h:mm AM/PM", "Jannewaris 01 2022 4:32 AM"},
{"43543.503206018519", "[$-462]m dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"},
{"43543.503206018519", "[$-462]mm dd yyyy h:mm AM/PM", "03 19 2019 12:04 PM"},
{"43543.503206018519", "[$-462]mmm dd yyyy h:mm AM/PM", "Mrt 19 2019 12:04 PM"},
{"43543.503206018519", "[$-462]mmmm dd yyyy h:mm AM/PM aaa", "Maart 19 2019 12:04 PM tii"},
{"43543.503206018519", "[$-462]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM tii"},
{"43543.503206018519", "[$-462]mmmmmm dd yyyy h:mm AM/PM dddd", "Maart 19 2019 12:04 PM tiisdei"},
{"44562.189571759256", "[$-67]mmm dd yyyy h:mm AM/PM", "sii 01 2022 4:32 AM"},
{"44562.189571759256", "[$-67]mmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"},
{"44562.189571759256", "[$-67]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-67]mmmmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"},
{"43543.503206018519", "[$-67]mmm dd yyyy h:mm AM/PM", "mbo 19 2019 12:04 PM"},
{"43543.503206018519", "[$-67]mmmm dd yyyy h:mm AM/PM aaa", "mbooy 19 2019 12:04 PM maw"},
{"43543.503206018519", "[$-67]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM maw"},
{"43543.503206018519", "[$-67]mmmmmm dd yyyy h:mm AM/PM dddd", "mbooy 19 2019 12:04 PM mawbaare"},
{"44562.189571759256", "[$-7C67]mmm dd yyyy h:mm AM/PM", "sii 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C67]mmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C67]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C67]mmmmmm dd yyyy h:mm AM/PM", "siilo 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C67]mmm dd yyyy h:mm AM/PM", "mbo 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C67]mmmm dd yyyy h:mm AM/PM aaa", "mbooy 19 2019 12:04 PM maw"},
{"43543.503206018519", "[$-7C67]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM maw"},
{"43543.503206018519", "[$-7C67]mmmmmm dd yyyy h:mm AM/PM dddd", "mbooy 19 2019 12:04 PM mawbaare"},
{"44562.189571759256", "[$-467]mmm dd yyyy h:mm AM/PM", "samw 01 2022 4:32 subaka"},
{"44562.189571759256", "[$-467]mmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 subaka"},
{"44562.189571759256", "[$-467]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 subaka"},
{"44562.189571759256", "[$-467]mmmmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 subaka"},
{"43543.503206018519", "[$-467]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 kikiiɗe"},
{"43543.503206018519", "[$-467]mmmm dd yyyy h:mm AM/PM aaa", "marsa 19 2019 12:04 kikiiɗe tal."},
{"43543.503206018519", "[$-467]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 kikiiɗe tal."},
{"43543.503206018519", "[$-467]mmmmmm dd yyyy h:mm AM/PM dddd", "marsa 19 2019 12:04 kikiiɗe talaata"},
{"44562.189571759256", "[$-867]mmm dd yyyy h:mm AM/PM", "samw 01 2022 4:32 AM"},
{"44562.189571759256", "[$-867]mmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 AM"},
{"44562.189571759256", "[$-867]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-867]mmmmmm dd yyyy h:mm AM/PM", "samwiee 01 2022 4:32 AM"},
{"43543.503206018519", "[$-867]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-867]mmmm dd yyyy h:mm AM/PM aaa", "marsa 19 2019 12:04 PM tal."},
{"43543.503206018519", "[$-867]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tal."},
{"43543.503206018519", "[$-867]mmmmmm dd yyyy h:mm AM/PM dddd", "marsa 19 2019 12:04 PM talaata"},
{"44562.189571759256", "[$-56]mmm dd yyyy h:mm AM/PM", "Xan. 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-56]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-56]mmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-56]mmmmm dd yyyy h:mm AM/PM", "M 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."},
{"44562.189571759256", "[$-56]mmm dd yyyy h:mm AM/PM", "Xan. 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmmm dd yyyy h:mm AM/PM", "X 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Xaneiro 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-56]mmm dd yyyy h:mm AM/PM aaa", "Mar. 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-56]mmmm dd yyyy h:mm AM/PM ddd", "Marzo 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-56]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 p.m. martes"},
{"43543.503206018519", "[$-56]mmmmmm dd yyyy h:mm AM/PM", "Marzo 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-456]mmm dd yyyy h:mm AM/PM aaa", "Mar. 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-456]mmmm dd yyyy h:mm AM/PM ddd", "Marzo 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-456]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 p.m. martes"},
{"44562.189571759256", "[$-37]mmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-37]mmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-37]mmmmm dd yyyy h:mm AM/PM", "\u10D8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-37]mmmmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"},
{"43543.503206018519", "[$-37]mmm dd yyyy h:mm AM/PM", "\u10DB\u10D0\u10E0 19 2019 12:04 PM"},
{"43543.503206018519", "[$-37]mmmm dd yyyy h:mm AM/PM aaa", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."},
{"43543.503206018519", "[$-37]mmmmm dd yyyy h:mm AM/PM ddd", "\u10DB 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."},
{"43543.503206018519", "[$-37]mmmmmm dd yyyy h:mm AM/PM dddd", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8"},
{"44562.189571759256", "[$-437]mmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-437]mmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-437]mmmmm dd yyyy h:mm AM/PM", "\u10D8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-437]mmmmmm dd yyyy h:mm AM/PM", "\u10D8\u10D0\u10DC\u10D5\u10D0\u10E0\u10D8 01 2022 4:32 AM"},
{"43543.503206018519", "[$-437]mmm dd yyyy h:mm AM/PM", "\u10DB\u10D0\u10E0 19 2019 12:04 PM"},
{"43543.503206018519", "[$-437]mmmm dd yyyy h:mm AM/PM aaa", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."},
{"43543.503206018519", "[$-437]mmmmm dd yyyy h:mm AM/PM ddd", "\u10DB 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8."},
{"43543.503206018519", "[$-437]mmmmmm dd yyyy h:mm AM/PM dddd", "\u10DB\u10D0\u10E0\u10E2\u10D8 19 2019 12:04 PM \u10E1\u10D0\u10DB\u10E8\u10D0\u10D1\u10D0\u10D7\u10D8"},
{"43543.503206018519", "[$-7]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-7]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-7]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"},
{"44562.189571759256", "[$-C07]mmm dd yyyy h:mm AM/PM aaa", "Jän 01 2022 4:32 AM Sa"},
{"44562.189571759256", "[$-C07]mmmm dd yyyy h:mm AM/PM ddd", "Jänner 01 2022 4:32 AM Sa"},
{"44562.189571759256", "[$-C07]mmmmm dd yyyy h:mm AM/PM dddd", "J 01 2022 4:32 AM Samstag"},
{"43543.503206018519", "[$-407]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-407]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-407]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"},
{"43543.503206018519", "[$-1407]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-1407]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-1407]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"},
{"43543.503206018519", "[$-1007]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-1007]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-1007]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"},
{"43543.503206018519", "[$-807]mmm dd yyyy h:mm AM/PM aaa", "Mär 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-807]mmmm dd yyyy h:mm AM/PM ddd", "März 19 2019 12:04 PM Di"},
{"43543.503206018519", "[$-807]mmmmm dd yyyy h:mm AM/PM dddd", "M 19 2019 12:04 PM Dienstag"},
{"44562.189571759256", "[$-8]mmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-8]mmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-8]mmmmm dd yyyy h:mm AM/PM", "\u0399 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-8]mmmmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"},
{"43543.503206018519", "[$-8]mmm dd yyyy h:mm AM/PM", "\u039C\u03B1\u03C1 19 2019 12:04 \u03BC\u03BC"},
{"43543.503206018519", "[$-8]mmmm dd yyyy h:mm AM/PM aaa", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"},
{"43543.503206018519", "[$-8]mmmmm dd yyyy h:mm AM/PM ddd", "\u039C 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"},
{"43543.503206018519", "[$-8]mmmmmm dd yyyy h:mm AM/PM dddd", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03AF\u03C4\u03B7"},
{"44562.189571759256", "[$-408]mmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-408]mmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-408]mmmmm dd yyyy h:mm AM/PM", "\u0399 01 2022 4:32 \u03C0\u03BC"},
{"44562.189571759256", "[$-408]mmmmmm dd yyyy h:mm AM/PM", "\u0399\u03B1\u03BD\u03BF\u03C5\u03AC\u03C1\u03B9\u03BF\u03C2 01 2022 4:32 \u03C0\u03BC"},
{"43543.503206018519", "[$-408]mmm dd yyyy h:mm AM/PM", "\u039C\u03B1\u03C1 19 2019 12:04 \u03BC\u03BC"},
{"43543.503206018519", "[$-408]mmmm dd yyyy h:mm AM/PM aaa", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"},
{"43543.503206018519", "[$-408]mmmmm dd yyyy h:mm AM/PM ddd", "\u039C 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03B9"},
{"43543.503206018519", "[$-408]mmmmmm dd yyyy h:mm AM/PM dddd", "\u039C\u03AC\u03C1\u03C4\u03B9\u03BF\u03C2 19 2019 12:04 \u03BC\u03BC \u03A4\u03C1\u03AF\u03C4\u03B7"},
{"44562.189571759256", "[$-6F]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6F]mmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6F]mmmmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-6F]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-6F]mmmm dd yyyy h:mm AM/PM aaa", "marsi 19 2019 12:04 PM marl."},
{"43543.503206018519", "[$-6F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM marl."},
{"43543.503206018519", "[$-6F]mmmmmm dd yyyy h:mm AM/PM dddd", "marsi 19 2019 12:04 PM marlunngorneq"},
{"44562.189571759256", "[$-46F]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46F]mmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46F]mmmmmm dd yyyy h:mm AM/PM", "januaari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-46F]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-46F]mmmm dd yyyy h:mm AM/PM aaa", "marsi 19 2019 12:04 PM marl."},
{"43543.503206018519", "[$-46F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM marl."},
{"43543.503206018519", "[$-46F]mmmmmm dd yyyy h:mm AM/PM dddd", "marsi 19 2019 12:04 PM marlunngorneq"},
{"44562.189571759256", "[$-74]mmm dd yyyy h:mm AM/PM", "jteĩ 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-74]mmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-74]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-74]mmmmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-74]mmm dd yyyy h:mm AM/PM", "japy 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-74]mmmm dd yyyy h:mm AM/PM aaa", "jasyapy 19 2019 12:04 p.m. apy"},
{"43543.503206018519", "[$-74]mmmmm dd yyyy h:mm AM/PM ddd", "j 19 2019 12:04 p.m. apy"},
{"43543.503206018519", "[$-74]mmmmmm dd yyyy h:mm AM/PM dddd", "jasyapy 19 2019 12:04 p.m. araapy"},
{"44562.189571759256", "[$-474]mmm dd yyyy h:mm AM/PM", "jteĩ 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-474]mmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-474]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-474]mmmmmm dd yyyy h:mm AM/PM", "jasyteĩ 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-474]mmm dd yyyy h:mm AM/PM", "japy 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-474]mmmm dd yyyy h:mm AM/PM aaa", "jasyapy 19 2019 12:04 p.m. apy"},
{"43543.503206018519", "[$-474]mmmmm dd yyyy h:mm AM/PM ddd", "j 19 2019 12:04 p.m. apy"},
{"43543.503206018519", "[$-474]mmmmmm dd yyyy h:mm AM/PM dddd", "jasyapy 19 2019 12:04 p.m. araapy"},
{"44562.189571759256", "[$-47]mmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-47]mmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-47]mmmmm dd yyyy h:mm AM/PM", "\u0A9C 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-47]mmmmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"43543.503206018519", "[$-47]mmm dd yyyy h:mm AM/PM", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"43543.503206018519", "[$-47]mmmm dd yyyy h:mm AM/PM aaa", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"},
{"43543.503206018519", "[$-47]mmmmm dd yyyy h:mm AM/PM ddd", "\u0AAE 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"},
{"43543.503206018519", "[$-47]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0"},
{"44562.189571759256", "[$-447]mmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-447]mmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-447]mmmmm dd yyyy h:mm AM/PM", "\u0A9C 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"44562.189571759256", "[$-447]mmmmmm dd yyyy h:mm AM/PM", "\u0A9C\u0ABE\u0AA8\u0ACD\u0AAF\u0AC1\u0A86\u0AB0\u0AC0 01 2022 4:32 \u0AAA\u0AC2\u0AB0\u0ACD\u0AB5 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"43543.503206018519", "[$-447]mmm dd yyyy h:mm AM/PM", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8"},
{"43543.503206018519", "[$-447]mmmm dd yyyy h:mm AM/PM aaa", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"},
{"43543.503206018519", "[$-447]mmmmm dd yyyy h:mm AM/PM ddd", "\u0AAE 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3"},
{"43543.503206018519", "[$-447]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0AAE\u0ABE\u0AB0\u0ACD\u0A9A 19 2019 12:04 \u0A89\u0AA4\u0ACD\u0AA4\u0AB0 \u0AAE\u0AA7\u0ACD\u0AAF\u0ABE\u0AB9\u0ACD\u0AA8 \u0AAE\u0A82\u0A97\u0AB3\u0AB5\u0ABE\u0AB0"},
{"44562.189571759256", "[$-68]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-68]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-68]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-68]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-68]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-68]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-68]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-68]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"},
{"44562.189571759256", "[$-7C68]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C68]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C68]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C68]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C68]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C68]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-7C68]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-7C68]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"},
{"44562.189571759256", "[$-468]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-468]mmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-468]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-468]mmmmmm dd yyyy h:mm AM/PM", "Janairu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-468]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-468]mmmm dd yyyy h:mm AM/PM aaa", "Maris 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-468]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tal"},
{"43543.503206018519", "[$-468]mmmmmm dd yyyy h:mm AM/PM dddd", "Maris 19 2019 12:04 PM Talata"},
{"44562.189571759256", "[$-75]mmm dd yyyy h:mm AM/PM", "Ian. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-75]mmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"},
{"44562.189571759256", "[$-75]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"},
{"44562.189571759256", "[$-75]mmmmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"},
{"43543.503206018519", "[$-75]mmm dd yyyy h:mm AM/PM", "Mal. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-75]mmmm dd yyyy h:mm AM/PM aaa", "Malaki 19 2019 12:04 PM P2"},
{"43543.503206018519", "[$-75]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM P2"},
{"43543.503206018519", "[$-75]mmmmmm dd yyyy h:mm AM/PM dddd", "Malaki 19 2019 12:04 PM Po\u02BBalua"},
{"44562.189571759256", "[$-475]mmm dd yyyy h:mm AM/PM", "Ian. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-475]mmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"},
{"44562.189571759256", "[$-475]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"},
{"44562.189571759256", "[$-475]mmmmmm dd yyyy h:mm AM/PM", "Ianuali 01 2022 4:32 AM"},
{"43543.503206018519", "[$-475]mmm dd yyyy h:mm AM/PM", "Mal. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-475]mmmm dd yyyy h:mm AM/PM aaa", "Malaki 19 2019 12:04 PM P2"},
{"43543.503206018519", "[$-475]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM P2"},
{"43543.503206018519", "[$-475]mmmmmm dd yyyy h:mm AM/PM dddd", "Malaki 19 2019 12:04 PM Po\u02BBalua"},
{"44562.189571759256", "[$-D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5 01 2022 4:32 AM"},
{"44562.189571759256", "[$-D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 AM"},
{"44562.189571759256", "[$-D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"},
{"43543.503206018519", "[$-D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E8\u05E5 19 2019 12:04 PM"},
{"43543.503206018519", "[$-D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9"},
{"44562.189571759256", "[$-40D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5 01 2022 4:32 AM"},
{"44562.189571759256", "[$-40D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"},
{"44562.189571759256", "[$-40D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 AM"},
{"44562.189571759256", "[$-40D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05E0\u05D5\u05D0\u05E8 01 2022 4:32 AM"},
{"43543.503206018519", "[$-40D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E8\u05E5 19 2019 12:04 PM"},
{"43543.503206018519", "[$-40D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-40D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-40D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E8\u05E5 19 2019 12:04 PM \u05D9\u05D5\u05DD \u05E9\u05DC\u05D9\u05E9\u05D9"},
{"44562.189571759256", "[$-39]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-39]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-39]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-39]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-39]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-39]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."},
{"43543.503206018519", "[$-39]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."},
{"43543.503206018519", "[$-39]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932\u0935\u093E\u0930"},
{"44562.189571759256", "[$-439]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-439]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-439]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-439]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-439]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-439]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."},
{"43543.503206018519", "[$-439]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932."},
{"43543.503206018519", "[$-439]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0902\u0917\u0932\u0935\u093E\u0930"},
{"44562.189571759256", "[$-E]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 de."},
{"44562.189571759256", "[$-E]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."},
{"44562.189571759256", "[$-E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 de."},
{"44562.189571759256", "[$-E]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."},
{"43543.503206018519", "[$-E]mmm dd yyyy h:mm AM/PM", "márc. 19 2019 12:04 du."},
{"43543.503206018519", "[$-E]mmmm dd yyyy h:mm AM/PM aaa", "március 19 2019 12:04 du. K"},
{"43543.503206018519", "[$-E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 du. K"},
{"43543.503206018519", "[$-E]mmmmmm dd yyyy h:mm AM/PM dddd", "március 19 2019 12:04 du. kedd"},
{"44562.189571759256", "[$-40E]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 de."},
{"44562.189571759256", "[$-40E]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."},
{"44562.189571759256", "[$-40E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 de."},
{"44562.189571759256", "[$-40E]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 de."},
{"43543.503206018519", "[$-40E]mmm dd yyyy h:mm AM/PM", "márc. 19 2019 12:04 du."},
{"43543.503206018519", "[$-40E]mmmm dd yyyy h:mm AM/PM aaa", "március 19 2019 12:04 du. K"},
{"43543.503206018519", "[$-40E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 du. K"},
{"43543.503206018519", "[$-40E]mmmmmm dd yyyy h:mm AM/PM dddd", "március 19 2019 12:04 du. kedd"},
{"44562.189571759256", "[$-F]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-F]mmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-F]mmmmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."},
{"43543.503206018519", "[$-F]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 e.h."},
{"43543.503206018519", "[$-F]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.h. þri."},
{"43543.503206018519", "[$-F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.h. þri."},
{"43543.503206018519", "[$-F]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.h. þriðjudagur"},
{"44562.189571759256", "[$-40F]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-40F]mmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-40F]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.h."},
{"44562.189571759256", "[$-40F]mmmmmm dd yyyy h:mm AM/PM", "janúar 01 2022 4:32 f.h."},
{"43543.503206018519", "[$-40F]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 e.h."},
{"43543.503206018519", "[$-40F]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.h. þri."},
{"43543.503206018519", "[$-40F]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.h. þri."},
{"43543.503206018519", "[$-40F]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.h. þriðjudagur"},
{"44562.189571759256", "[$-70]mmm dd yyyy h:mm AM/PM", "Jen 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-70]mmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-70]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-70]mmmmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."},
{"43543.503206018519", "[$-70]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 P.M."},
{"43543.503206018519", "[$-70]mmmm dd yyyy h:mm AM/PM aaa", "Machị 19 2019 12:04 P.M. Tiu"},
{"43543.503206018519", "[$-70]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 P.M. Tiu"},
{"43543.503206018519", "[$-70]mmmmmm dd yyyy h:mm AM/PM dddd", "Machị 19 2019 12:04 P.M. Tiuzdee"},
{"44562.189571759256", "[$-470]mmm dd yyyy h:mm AM/PM", "Jen 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-470]mmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-470]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 A.M."},
{"44562.189571759256", "[$-470]mmmmmm dd yyyy h:mm AM/PM", "Jenụwarị 01 2022 4:32 A.M."},
{"43543.503206018519", "[$-470]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 P.M."},
{"43543.503206018519", "[$-470]mmmm dd yyyy h:mm AM/PM aaa", "Machị 19 2019 12:04 P.M. Tiu"},
{"43543.503206018519", "[$-470]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 P.M. Tiu"},
{"43543.503206018519", "[$-470]mmmmmm dd yyyy h:mm AM/PM dddd", "Machị 19 2019 12:04 P.M. Tiuzdee"},
{"44562.189571759256", "[$-21]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-21]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-21]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-21]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-21]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-21]mmmm dd yyyy h:mm AM/PM aaa", "Maret 19 2019 12:04 PM Sel"},
{"43543.503206018519", "[$-21]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sel"},
{"43543.503206018519", "[$-21]mmmmmm dd yyyy h:mm AM/PM dddd", "Maret 19 2019 12:04 PM Selasa"},
{"44562.189571759256", "[$-421]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-421]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-421]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-421]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-421]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-421]mmmm dd yyyy h:mm AM/PM aaa", "Maret 19 2019 12:04 PM Sel"},
{"43543.503206018519", "[$-421]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sel"},
{"43543.503206018519", "[$-421]mmmmmm dd yyyy h:mm AM/PM dddd", "Maret 19 2019 12:04 PM Selasa"},
{"44562.189571759256", "[$-5D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-5D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"},
{"43543.503206018519", "[$-5D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-5D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-5D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"},
{"44562.189571759256", "[$-7C5D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C5D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C5D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-7C5D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-7C5D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"},
{"44562.189571759256", "[$-85D]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85D]mmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85D]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85D]mmmmmm dd yyyy h:mm AM/PM", "Jaannuari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-85D]mmm dd yyyy h:mm AM/PM", "Mas 19 2019 12:04 PM"},
{"43543.503206018519", "[$-85D]mmmm dd yyyy h:mm AM/PM aaa", "Maatsi 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-85D]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Aip"},
{"43543.503206018519", "[$-85D]mmmmmm dd yyyy h:mm AM/PM dddd", "Maatsi 19 2019 12:04 PM Aippiq"},
{"44562.189571759256", "[$-785D]mmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4 01 2022 4:32 AM"},
{"44562.189571759256", "[$-785D]mmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"},
{"44562.189571759256", "[$-785D]mmmmm dd yyyy h:mm AM/PM", "\u152E 01 2022 4:32 AM"},
{"44562.189571759256", "[$-785D]mmmmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"},
{"43543.503206018519", "[$-785D]mmm dd yyyy h:mm AM/PM", "\u14AB\u1466\u14EF 19 2019 12:04 PM"},
{"43543.503206018519", "[$-785D]mmmm dd yyyy h:mm AM/PM aaa", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431"},
{"43543.503206018519", "[$-785D]mmmmm dd yyyy h:mm AM/PM ddd", "\u14AB 19 2019 12:04 PM \u140A\u1403\u1449\u1431"},
{"43543.503206018519", "[$-785D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431\u1585"},
{"44562.189571759256", "[$-45D]mmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45D]mmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45D]mmmmm dd yyyy h:mm AM/PM", "\u152E 01 2022 4:32 AM"},
{"44562.189571759256", "[$-45D]mmmmmm dd yyyy h:mm AM/PM", "\u152E\u14D0\u14C4\u140A\u1546 01 2022 4:32 AM"},
{"43543.503206018519", "[$-45D]mmm dd yyyy h:mm AM/PM", "\u14AB\u1466\u14EF 19 2019 12:04 PM"},
{"43543.503206018519", "[$-45D]mmmm dd yyyy h:mm AM/PM aaa", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431"},
{"43543.503206018519", "[$-45D]mmmmm dd yyyy h:mm AM/PM ddd", "\u14AB 19 2019 12:04 PM \u140A\u1403\u1449\u1431"},
{"43543.503206018519", "[$-45D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u14AB\u1466\u14EF 19 2019 12:04 PM \u140A\u1403\u1449\u1431\u1585"},
{"44562.189571759256", "[$-3C]mmm dd yyyy h:mm AM/PM", "Ean 01 2022 4:32 r.n."},
{"44593.189571759256", "[$-3C]mmm dd yyyy h:mm AM/PM", "Feabh 01 2022 4:32 r.n."},
{"44621.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."},
{"44652.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Aib 01 2022 4:32 r.n."},
{"44682.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Beal 01 2022 4:32 r.n."},
{"44713.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Meith 01 2022 4:32 r.n."},
{"44743.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."},
{"44774.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Lún 01 2022 4:32 r.n."},
{"44805.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "MFómh 01 2022 4:32 r.n."},
{"44835.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "DFómh 01 2022 4:32 r.n."},
{"44866.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Samh 01 2022 4:32 r.n."},
{"44896.18957170139", "[$-3C]mmm dd yyyy h:mm AM/PM", "Noll 01 2022 4:32 r.n."},
{"44562.189571759256", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Eanáir 01 2022 4:32 r.n."},
{"44593.189571759256", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Feabhra 01 2022 4:32 r.n."},
{"44621.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."},
{"44652.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Aibreán 01 2022 4:32 r.n."},
{"44682.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Bealtaine 01 2022 4:32 r.n."},
{"44713.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Meitheamh 01 2022 4:32 r.n."},
{"44743.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."},
{"44774.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Lúnasa 01 2022 4:32 r.n."},
{"44805.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Meán Fómhair 01 2022 4:32 r.n."},
{"44835.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Deireadh Fómhair 01 2022 4:32 r.n."},
{"44866.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Samhain 01 2022 4:32 r.n."},
{"44896.18957170139", "[$-3C]mmmm dd yyyy h:mm AM/PM", "Nollaig 01 2022 4:32 r.n."},
{"44562.189571759256", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 r.n."},
{"44593.189571759256", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 r.n."},
{"44621.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."},
{"44652.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 r.n."},
{"44682.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "B 01 2022 4:32 r.n."},
{"44713.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."},
{"44743.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 r.n."},
{"44774.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "L 01 2022 4:32 r.n."},
{"44805.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 r.n."},
{"44835.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM aaa", "D 01 2022 4:32 r.n. Sath"},
{"44866.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM ddd", "S 01 2022 4:32 r.n. Máirt"},
{"44896.18957170139", "[$-3C]mmmmm dd yyyy h:mm AM/PM dddd", "N 01 2022 4:32 r.n. Déardaoin"},
{"44562.189571759256", "[$-83C]mmm dd yyyy h:mm AM/PM", "Ean 01 2022 4:32 r.n."},
{"44593.189571759256", "[$-83C]mmm dd yyyy h:mm AM/PM", "Feabh 01 2022 4:32 r.n."},
{"44621.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."},
{"44652.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Aib 01 2022 4:32 r.n."},
{"44682.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Beal 01 2022 4:32 r.n."},
{"44713.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Meith 01 2022 4:32 r.n."},
{"44743.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."},
{"44774.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Lún 01 2022 4:32 r.n."},
{"44805.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "MFómh 01 2022 4:32 r.n."},
{"44835.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "DFómh 01 2022 4:32 r.n."},
{"44866.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Samh 01 2022 4:32 r.n."},
{"44896.18957170139", "[$-83C]mmm dd yyyy h:mm AM/PM", "Noll 01 2022 4:32 r.n."},
{"44562.189571759256", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Eanáir 01 2022 4:32 r.n."},
{"44593.189571759256", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Feabhra 01 2022 4:32 r.n."},
{"44621.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Márta 01 2022 4:32 r.n."},
{"44652.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Aibreán 01 2022 4:32 r.n."},
{"44682.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Bealtaine 01 2022 4:32 r.n."},
{"44713.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Meitheamh 01 2022 4:32 r.n."},
{"44743.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Iúil 01 2022 4:32 r.n."},
{"44774.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Lúnasa 01 2022 4:32 r.n."},
{"44805.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM", "Meán Fómhair 01 2022 4:32 r.n."},
{"44835.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM aaa", "Deireadh Fómhair 01 2022 4:32 r.n. Sath"},
{"44866.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM ddd", "Samhain 01 2022 4:32 r.n. Máirt"},
{"44896.18957170139", "[$-83C]mmmm dd yyyy h:mm AM/PM dddd", "Nollaig 01 2022 4:32 r.n. Déardaoin"},
{"43543.503206018519", "[$-10]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-10]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-10]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"},
{"43543.503206018519", "[$-410]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-410]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-410]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"},
{"43543.503206018519", "[$-810]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-810]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-810]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martedì"},
{"43543.503206018519", "[$-11]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 午後 火"},
{"43543.503206018519", "[$-11]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 午後 火"},
{"43543.503206018519", "[$-11]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 午後 火曜日"},
{"43543.503206018519", "[$-411]mmm dd yyyy h:mm AM/PM aaa", "3月 19 2019 12:04 午後 火"},
{"43543.503206018519", "[$-411]mmmm dd yyyy h:mm AM/PM ddd", "3月 19 2019 12:04 午後 火"},
{"43543.503206018519", "[$-411]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 午後 火曜日"},
{"44562.189571759256", "[$-4B]mmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-4B]mmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-4B]mmmmm dd yyyy h:mm AM/PM", "\u0C9C 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-4B]mmmmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"43543.503206018519", "[$-4B]mmm dd yyyy h:mm AM/PM", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8"},
{"43543.503206018519", "[$-4B]mmmm dd yyyy h:mm AM/PM aaa", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."},
{"43543.503206018519", "[$-4B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0CAE 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."},
{"43543.503206018519", "[$-4B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0"},
{"44562.189571759256", "[$-44B]mmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-44B]mmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-44B]mmmmm dd yyyy h:mm AM/PM", "\u0C9C 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"44562.189571759256", "[$-44B]mmmmmm dd yyyy h:mm AM/PM", "\u0C9C\u0CA8\u0CB5\u0CB0\u0CBF 01 2022 4:32 \u0CAA\u0CC2\u0CB0\u0CCD\u0CB5\u0CBE\u0CB9\u0CCD\u0CA8"},
{"43543.503206018519", "[$-44B]mmm dd yyyy h:mm AM/PM", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8"},
{"43543.503206018519", "[$-44B]mmmm dd yyyy h:mm AM/PM aaa", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."},
{"43543.503206018519", "[$-44B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0CAE 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3."},
{"43543.503206018519", "[$-44B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0CAE\u0CBE\u0CB0\u0CCD\u0C9A\u0CCD 19 2019 12:04 \u0C85\u0CAA\u0CB0\u0CBE\u0CB9\u0CCD\u0CA8 \u0CAE\u0C82\u0C97\u0CB3\u0CB5\u0CBE\u0CB0"},
{"44562.189571759256", "[$-471]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-471]mmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"},
{"44562.189571759256", "[$-471]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-471]mmmmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"},
{"43543.503206018519", "[$-471]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-471]mmmm dd yyyy h:mm AM/PM aaa", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-471]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-471]mmmmmm dd yyyy h:mm AM/PM dddd", "March 19 2019 12:04 PM Tuesday"},
{"44562.189571759256", "[$-60]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-60]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-60]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-60]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-60]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-60]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"43543.503206018519", "[$-60]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"43543.503206018519", "[$-60]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"44562.189571759256", "[$-460]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-460]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-460]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-460]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0624\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-460]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-460]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"43543.503206018519", "[$-460]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"43543.503206018519", "[$-460]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0655\u0686 19 2019 12:04 PM \u0628\u06C6\u0645\u0648\u0627\u0631"},
{"44562.189571759256", "[$-860]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-860]mmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"},
{"44562.189571759256", "[$-860]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-860]mmmmmm dd yyyy h:mm AM/PM", "January 01 2022 4:32 AM"},
{"43543.503206018519", "[$-860]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-860]mmmm dd yyyy h:mm AM/PM aaa", "March 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-860]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-860]mmmmmm dd yyyy h:mm AM/PM dddd", "March 19 2019 12:04 PM Tuesday"},
{"44562.189571759256", "[$-3F]mmm dd yyyy h:mm AM/PM", "қаң 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3F]mmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3F]mmmmm dd yyyy h:mm AM/PM", "Қ 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3F]mmmmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"},
{"43543.503206018519", "[$-3F]mmm dd yyyy h:mm AM/PM", "нау 19 2019 12:04 PM"},
{"43543.503206018519", "[$-3F]mmmm dd yyyy h:mm AM/PM aaa", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439"},
{"43543.503206018519", "[$-3F]mmmmm dd yyyy h:mm AM/PM ddd", "Н 19 2019 12:04 PM \u0441\u0435\u0439"},
{"43543.503206018519", "[$-3F]mmmmmm dd yyyy h:mm AM/PM dddd", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456"},
{"44562.189571759256", "[$-43F]mmm dd yyyy h:mm AM/PM", "қаң 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43F]mmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43F]mmmmm dd yyyy h:mm AM/PM", "Қ 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43F]mmmmmm dd yyyy h:mm AM/PM", "Қаңтар 01 2022 4:32 AM"},
{"43543.503206018519", "[$-43F]mmm dd yyyy h:mm AM/PM", "нау 19 2019 12:04 PM"},
{"43543.503206018519", "[$-43F]mmmm dd yyyy h:mm AM/PM aaa", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439"},
{"43543.503206018519", "[$-43F]mmmmm dd yyyy h:mm AM/PM ddd", "Н 19 2019 12:04 PM \u0441\u0435\u0439"},
{"43543.503206018519", "[$-43F]mmmmmm dd yyyy h:mm AM/PM dddd", "Наурыз 19 2019 12:04 PM \u0441\u0435\u0439\u0441\u0435\u043D\u0431\u0456"},
{"44562.189571759256", "[$-53]mmm dd yyyy h:mm AM/PM", "\u17E1 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-53]mmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-53]mmmmm dd yyyy h:mm AM/PM", "\u1798 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-53]mmmmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"43543.503206018519", "[$-53]mmm dd yyyy h:mm AM/PM", "\u17E3 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785"},
{"43543.503206018519", "[$-53]mmmm dd yyyy h:mm AM/PM aaa", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."},
{"43543.503206018519", "[$-53]mmmmm dd yyyy h:mm AM/PM ddd", "\u1798 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."},
{"43543.503206018519", "[$-53]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A"},
{"44562.189571759256", "[$-453]mmm dd yyyy h:mm AM/PM", "\u17E1 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-453]mmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-453]mmmmm dd yyyy h:mm AM/PM", "\u1798 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"44562.189571759256", "[$-453]mmmmmm dd yyyy h:mm AM/PM", "\u1798\u1780\u179A\u17B6 01 2022 4:32 \u1796\u17D2\u179A\u17B9\u1780"},
{"43543.503206018519", "[$-453]mmm dd yyyy h:mm AM/PM", "\u17E3 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785"},
{"43543.503206018519", "[$-453]mmmm dd yyyy h:mm AM/PM aaa", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."},
{"43543.503206018519", "[$-453]mmmmm dd yyyy h:mm AM/PM ddd", "\u1798 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u17A2."},
{"43543.503206018519", "[$-453]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1798\u17B7\u1793\u17B6 19 2019 12:04 \u179B\u17D2\u1784\u17B6\u1785 \u1790\u17D2\u1784\u17C3\u17A2\u1784\u17D2\u1782\u17B6\u179A"},
{"44562.189571759256", "[$-86]mmm dd yyyy h:mm AM/PM", "nab'e 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-86]mmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-86]mmmmm dd yyyy h:mm AM/PM", "n 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-86]mmmmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-86]mmm dd yyyy h:mm AM/PM", "urox 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-86]mmmm dd yyyy h:mm AM/PM aaa", "urox ik' 19 2019 12:04 p.m. oxq'"},
{"43543.503206018519", "[$-86]mmmmm dd yyyy h:mm AM/PM ddd", "u 19 2019 12:04 p.m. oxq'"},
{"43543.503206018519", "[$-86]mmmmmm dd yyyy h:mm AM/PM dddd", "urox ik' 19 2019 12:04 p.m. oxq'ij"},
{"44562.189571759256", "[$-486]mmm dd yyyy h:mm AM/PM", "nab'e 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-486]mmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-486]mmmmm dd yyyy h:mm AM/PM", "n 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-486]mmmmmm dd yyyy h:mm AM/PM", "nab'e ik' 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-486]mmm dd yyyy h:mm AM/PM", "urox 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-486]mmmm dd yyyy h:mm AM/PM aaa", "urox ik' 19 2019 12:04 p.m. oxq'"},
{"43543.503206018519", "[$-486]mmmmm dd yyyy h:mm AM/PM ddd", "u 19 2019 12:04 p.m. oxq'"},
{"43543.503206018519", "[$-486]mmmmmm dd yyyy h:mm AM/PM dddd", "urox ik' 19 2019 12:04 p.m. oxq'ij"},
{"44562.189571759256", "[$-87]mmm dd yyyy h:mm AM/PM", "mut. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-87]mmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"},
{"44562.189571759256", "[$-87]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44562.189571759256", "[$-87]mmmmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"},
{"43543.503206018519", "[$-87]mmm dd yyyy h:mm AM/PM", "wer. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-87]mmmm dd yyyy h:mm AM/PM aaa", "Werurwe 19 2019 12:04 PM kab."},
{"43543.503206018519", "[$-87]mmmmm dd yyyy h:mm AM/PM ddd", "W 19 2019 12:04 PM kab."},
{"43543.503206018519", "[$-87]mmmmmm dd yyyy h:mm AM/PM dddd", "Werurwe 19 2019 12:04 PM Ku wa kabiri"},
{"44562.189571759256", "[$-487]mmm dd yyyy h:mm AM/PM", "mut. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-487]mmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"},
{"44562.189571759256", "[$-487]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44562.189571759256", "[$-487]mmmmmm dd yyyy h:mm AM/PM", "Mutarama 01 2022 4:32 AM"},
{"43543.503206018519", "[$-487]mmm dd yyyy h:mm AM/PM", "wer. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-487]mmmm dd yyyy h:mm AM/PM aaa", "Werurwe 19 2019 12:04 PM kab."},
{"43543.503206018519", "[$-487]mmmmm dd yyyy h:mm AM/PM ddd", "W 19 2019 12:04 PM kab."},
{"43543.503206018519", "[$-487]mmmmmm dd yyyy h:mm AM/PM dddd", "Werurwe 19 2019 12:04 PM Ku wa kabiri"},
{"44562.189571759256", "[$-41]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-41]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PM"},
{"43543.503206018519", "[$-41]mmmm dd yyyy h:mm AM/PM aaa", "Machi 19 2019 12:04 PM Jnn"},
{"43543.503206018519", "[$-41]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Jnn"},
{"43543.503206018519", "[$-41]mmmmmm dd yyyy h:mm AM/PM dddd", "Machi 19 2019 12:04 PM Jumanne"},
{"44562.189571759256", "[$-441]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-441]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-441]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-441]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-441]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PM"},
{"43543.503206018519", "[$-441]mmmm dd yyyy h:mm AM/PM aaa", "Machi 19 2019 12:04 PM Jnn"},
{"43543.503206018519", "[$-441]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Jnn"},
{"43543.503206018519", "[$-441]mmmmmm dd yyyy h:mm AM/PM dddd", "Machi 19 2019 12:04 PM Jumanne"},
{"44562.189571759256", "[$-57]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-57]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-57]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-57]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"43543.503206018519", "[$-57]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-57]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-57]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-57]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u093E\u0930"},
{"44562.189571759256", "[$-457]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-457]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-457]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-457]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"43543.503206018519", "[$-457]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-457]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-457]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-457]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u093E\u0930"},
{"43543.503206018519", "[$-12]mmm dd yyyy h:mm AM/PM aaa", "3 19 2019 12:04 오후 화"},
{"43543.503206018519", "[$-12]mmmm dd yyyy h:mm AM/PM ddd", "3월 19 2019 12:04 오후 화"},
{"43543.503206018519", "[$-12]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 오후 화요일"},
{"43543.503206018519", "[$-412]mmm dd yyyy h:mm AM/PM aaa", "3 19 2019 12:04 오후 화"},
{"43543.503206018519", "[$-412]mmmm dd yyyy h:mm AM/PM ddd", "3월 19 2019 12:04 오후 화"},
{"43543.503206018519", "[$-412]mmmmm dd yyyy h:mm AM/PM dddd", "3 19 2019 12:04 오후 화요일"},
{"44562.189571759256", "[$-40]mmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-40]mmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-40]mmmmm dd yyyy h:mm AM/PM", "\u042F 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-40]mmmmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"},
{"43543.503206018519", "[$-40]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 \u0442\u043A"},
{"43543.503206018519", "[$-40]mmmm dd yyyy h:mm AM/PM aaa", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."},
{"43543.503206018519", "[$-40]mmmmm dd yyyy h:mm AM/PM ddd", "\u041C 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."},
{"43543.503206018519", "[$-40]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438"},
{"44562.189571759256", "[$-440]mmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-440]mmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-440]mmmmm dd yyyy h:mm AM/PM", "\u042F 01 2022 4:32 \u0442\u04A3"},
{"44562.189571759256", "[$-440]mmmmmm dd yyyy h:mm AM/PM", "\u042F\u043D\u0432\u0430\u0440\u044C 01 2022 4:32 \u0442\u04A3"},
{"43543.503206018519", "[$-440]mmm dd yyyy h:mm AM/PM", "\u041C\u0430\u0440 19 2019 12:04 \u0442\u043A"},
{"43543.503206018519", "[$-440]mmmm dd yyyy h:mm AM/PM aaa", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."},
{"43543.503206018519", "[$-440]mmmmm dd yyyy h:mm AM/PM ddd", "\u041C 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448."},
{"43543.503206018519", "[$-440]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041C\u0430\u0440\u0442 19 2019 12:04 \u0442\u043A \u0448\u0435\u0439\u0448\u0435\u043C\u0431\u0438"},
{"44562.189571759256", "[$-54]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E81. 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-54]mmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-54]mmmmm dd yyyy h:mm AM/PM", "\u0EA1 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-54]mmmmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"43543.503206018519", "[$-54]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E99. 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87"},
{"43543.503206018519", "[$-54]mmmm dd yyyy h:mm AM/PM aaa", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"43543.503206018519", "[$-54]mmmmm dd yyyy h:mm AM/PM ddd", "\u0EA1 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"43543.503206018519", "[$-54]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"44562.189571759256", "[$-454]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E81. 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-454]mmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-454]mmmmm dd yyyy h:mm AM/PM", "\u0EA1 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"44562.189571759256", "[$-454]mmmmmm dd yyyy h:mm AM/PM", "\u0EA1\u0EB1\u0E87\u0E81\u0EAD\u0E99 01 2022 4:32 \u0E81\u0EC8\u0EAD\u0E99\u0E97\u0EC8\u0EBD\u0E87"},
{"43543.503206018519", "[$-454]mmm dd yyyy h:mm AM/PM", "\u0EA1.\u0E99. 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87"},
{"43543.503206018519", "[$-454]mmmm dd yyyy h:mm AM/PM aaa", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"43543.503206018519", "[$-454]mmmmm dd yyyy h:mm AM/PM ddd", "\u0EA1 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"43543.503206018519", "[$-454]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0EA1\u0EB5\u0E99\u0EB2 19 2019 12:04 \u0EAB\u0EBC\u0EB1\u0E87\u0E97\u0EC8\u0EBD\u0E87 \u0EA7\u0EB1\u0E99\u0EAD\u0EB1\u0E87\u0E84\u0EB2\u0E99"},
{"44562.189571759256", "[$-476]mmm dd yyyy h:mm AM/PM", "Ian 01 2022 4:32 AM"},
{"44562.189571759256", "[$-476]mmmm dd yyyy h:mm AM/PM", "Ianuarius 01 2022 4:32 AM"},
{"44562.189571759256", "[$-476]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 AM"},
{"44562.189571759256", "[$-476]mmmmmm dd yyyy h:mm AM/PM", "Ianuarius 01 2022 4:32 AM"},
{"43543.503206018519", "[$-476]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-476]mmmm dd yyyy h:mm AM/PM aaa", "Martius 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-476]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Mar"},
{"43543.503206018519", "[$-476]mmmmmm dd yyyy h:mm AM/PM dddd", "Martius 19 2019 12:04 PM Martis"},
{"44562.189571759256", "[$-26]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-26]mmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-26]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-26]mmmmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."},
{"43543.503206018519", "[$-26]mmm dd yyyy h:mm AM/PM", "marts 19 2019 12:04 pēcp."},
{"43543.503206018519", "[$-26]mmmm dd yyyy h:mm AM/PM aaa", "marts 19 2019 12:04 pēcp. otrd."},
{"43543.503206018519", "[$-26]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pēcp. otrd."},
{"43543.503206018519", "[$-26]mmmmmm dd yyyy h:mm AM/PM dddd", "marts 19 2019 12:04 pēcp. otrdiena"},
{"44562.189571759256", "[$-426]mmm dd yyyy h:mm AM/PM", "janv. 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-426]mmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-426]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 priekšp."},
{"44562.189571759256", "[$-426]mmmmmm dd yyyy h:mm AM/PM", "janvāris 01 2022 4:32 priekšp."},
{"43543.503206018519", "[$-426]mmm dd yyyy h:mm AM/PM", "marts 19 2019 12:04 pēcp."},
{"43543.503206018519", "[$-426]mmmm dd yyyy h:mm AM/PM aaa", "marts 19 2019 12:04 pēcp. otrd."},
{"43543.503206018519", "[$-426]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pēcp. otrd."},
{"43543.503206018519", "[$-426]mmmmmm dd yyyy h:mm AM/PM dddd", "marts 19 2019 12:04 pēcp. otrdiena"},
{"44562.189571759256", "[$-27]mmm dd yyyy h:mm AM/PM", "saus. 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-27]mmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-27]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-27]mmmmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"},
{"43543.503206018519", "[$-27]mmm dd yyyy h:mm AM/PM", "kov. 19 2019 12:04 popiet"},
{"43543.503206018519", "[$-27]mmmm dd yyyy h:mm AM/PM aaa", "kovas 19 2019 12:04 popiet an"},
{"43543.503206018519", "[$-27]mmmmm dd yyyy h:mm AM/PM ddd", "k 19 2019 12:04 popiet an"},
{"43543.503206018519", "[$-27]mmmmmm dd yyyy h:mm AM/PM dddd", "kovas 19 2019 12:04 popiet antradienis"},
{"44562.189571759256", "[$-427]mmm dd yyyy h:mm AM/PM", "saus. 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-427]mmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-427]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 priešpiet"},
{"44562.189571759256", "[$-427]mmmmmm dd yyyy h:mm AM/PM", "sausis 01 2022 4:32 priešpiet"},
{"43543.503206018519", "[$-427]mmm dd yyyy h:mm AM/PM", "kov. 19 2019 12:04 popiet"},
{"43543.503206018519", "[$-427]mmmm dd yyyy h:mm AM/PM aaa", "kovas 19 2019 12:04 popiet an"},
{"43543.503206018519", "[$-427]mmmmm dd yyyy h:mm AM/PM ddd", "k 19 2019 12:04 popiet an"},
{"43543.503206018519", "[$-427]mmmmmm dd yyyy h:mm AM/PM dddd", "kovas 19 2019 12:04 popiet antradienis"},
{"44562.189571759256", "[$-7C2E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C2E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C2E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C2E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C2E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C2E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 PM wa\u0142"},
{"43543.503206018519", "[$-7C2E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wa\u0142"},
{"43543.503206018519", "[$-7C2E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 PM wa\u0142tora"},
{"44562.189571759256", "[$-82E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-82E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 PM"},
{"43543.503206018519", "[$-82E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 PM wa\u0142"},
{"43543.503206018519", "[$-82E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wa\u0142"},
{"43543.503206018519", "[$-82E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 PM wa\u0142tora"},
{"44562.189571759256", "[$-6E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6E]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6E]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-6E]mmm dd yyyy h:mm AM/PM", "Mäe 19 2019 12:04 PM"},
{"43543.503206018519", "[$-6E]mmmm dd yyyy h:mm AM/PM aaa", "Mäerz 19 2019 12:04 PM Dën"},
{"43543.503206018519", "[$-6E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Dën"},
{"43543.503206018519", "[$-6E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mäerz 19 2019 12:04 PM Dënschdeg"},
{"44562.189571759256", "[$-46E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46E]mmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46E]mmmmmm dd yyyy h:mm AM/PM", "Januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-46E]mmm dd yyyy h:mm AM/PM", "Mäe 19 2019 12:04 PM"},
{"43543.503206018519", "[$-46E]mmmm dd yyyy h:mm AM/PM aaa", "Mäerz 19 2019 12:04 PM Dën"},
{"43543.503206018519", "[$-46E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Dën"},
{"43543.503206018519", "[$-46E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mäerz 19 2019 12:04 PM Dënschdeg"},
{"44562.189571759256", "[$-2F]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-2F]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-2F]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-2F]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"43543.503206018519", "[$-2F]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 \u043F\u043E\u043F\u043B."},
{"43543.503206018519", "[$-2F]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."},
{"43543.503206018519", "[$-2F]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."},
{"43543.503206018519", "[$-2F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"44562.189571759256", "[$-42F]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-42F]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-42F]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"44562.189571759256", "[$-42F]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440\u0438 01 2022 4:32 \u043F\u0440\u0435\u0442\u043F\u043B."},
{"43543.503206018519", "[$-42F]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 \u043F\u043E\u043F\u043B."},
{"43543.503206018519", "[$-42F]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."},
{"43543.503206018519", "[$-42F]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442."},
{"43543.503206018519", "[$-42F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u043F\u043E\u043F\u043B. \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"44562.189571759256", "[$-3E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"},
{"44562.189571759256", "[$-3E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"44562.189571759256", "[$-3E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"},
{"44562.189571759256", "[$-3E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"43543.503206018519", "[$-3E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"},
{"43543.503206018519", "[$-3E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-3E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-3E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"},
{"44562.189571759256", "[$-83E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"},
{"44562.189571759256", "[$-83E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"44562.189571759256", "[$-83E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"},
{"44562.189571759256", "[$-83E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"43543.503206018519", "[$-83E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"},
{"43543.503206018519", "[$-83E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-83E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-83E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"},
{"44562.189571759256", "[$-43E]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 PG"},
{"44562.189571759256", "[$-43E]mmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"44562.189571759256", "[$-43E]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 PG"},
{"44562.189571759256", "[$-43E]mmmmmm dd yyyy h:mm AM/PM", "Januari 01 2022 4:32 PG"},
{"43543.503206018519", "[$-43E]mmm dd yyyy h:mm AM/PM", "Mac 19 2019 12:04 PTG"},
{"43543.503206018519", "[$-43E]mmmm dd yyyy h:mm AM/PM aaa", "Mac 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-43E]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PTG Sel"},
{"43543.503206018519", "[$-43E]mmmmmm dd yyyy h:mm AM/PM dddd", "Mac 19 2019 12:04 PTG Selasa"},
{"44562.189571759256", "[$-4C]mmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4C]mmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4C]mmmmm dd yyyy h:mm AM/PM", "\u0D1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4C]mmmmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"},
{"43543.503206018519", "[$-4C]mmm dd yyyy h:mm AM/PM", "\u0D2E\u0D3E\u0D7C 19 2019 12:04 PM"},
{"43543.503206018519", "[$-4C]mmmm dd yyyy h:mm AM/PM aaa", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"},
{"43543.503206018519", "[$-4C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0D2E 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"},
{"43543.503206018519", "[$-4C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A"},
{"44562.189571759256", "[$-44C]mmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44C]mmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44C]mmmmm dd yyyy h:mm AM/PM", "\u0D1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44C]mmmmmm dd yyyy h:mm AM/PM", "\u0D1C\u0D28\u0D41\u0D35\u0D30\u0D3F 01 2022 4:32 AM"},
{"43543.503206018519", "[$-44C]mmm dd yyyy h:mm AM/PM", "\u0D2E\u0D3E\u0D7C 19 2019 12:04 PM"},
{"43543.503206018519", "[$-44C]mmmm dd yyyy h:mm AM/PM aaa", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"},
{"43543.503206018519", "[$-44C]mmmmm dd yyyy h:mm AM/PM ddd", "\u0D2E 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35"},
{"43543.503206018519", "[$-44C]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0D2E\u0D3E\u0D30\u0D4D\u200D\u200C\u0D1A\u0D4D\u0D1A\u0D4D 19 2019 12:04 PM \u0D1A\u0D4A\u0D35\u0D4D\u0D35\u0D3E\u0D34\u0D4D\u0D1A"},
{"44562.189571759256", "[$-3A]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3A]mmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3A]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-3A]mmmmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-3A]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-3A]mmmm dd yyyy h:mm AM/PM aaa", "Marzu 19 2019 12:04 PM Tli"},
{"43543.503206018519", "[$-3A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tli"},
{"43543.503206018519", "[$-3A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzu 19 2019 12:04 PM It-Tlieta"},
{"44562.189571759256", "[$-43A]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43A]mmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43A]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-43A]mmmmmm dd yyyy h:mm AM/PM", "Jannar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-43A]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-43A]mmmm dd yyyy h:mm AM/PM aaa", "Marzu 19 2019 12:04 PM Tli"},
{"43543.503206018519", "[$-43A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Tli"},
{"43543.503206018519", "[$-43A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzu 19 2019 12:04 PM It-Tlieta"},
{"44562.189571759256", "[$-81]mmm dd yyyy h:mm AM/PM", "Kohi 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-81]mmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-81]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-81]mmmmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-81]mmm dd yyyy h:mm AM/PM", "Pou 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-81]mmmm dd yyyy h:mm AM/PM aaa", "Poutūterangi 19 2019 12:04 p.m. Tū"},
{"43543.503206018519", "[$-81]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Tū"},
{"43543.503206018519", "[$-81]mmmmmm dd yyyy h:mm AM/PM dddd", "Poutūterangi 19 2019 12:04 p.m. Rātū"},
{"44562.189571759256", "[$-481]mmm dd yyyy h:mm AM/PM", "Kohi 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-481]mmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-481]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-481]mmmmmm dd yyyy h:mm AM/PM", "Kohitātea 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-481]mmm dd yyyy h:mm AM/PM", "Pou 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-481]mmmm dd yyyy h:mm AM/PM aaa", "Poutūterangi 19 2019 12:04 p.m. Tū"},
{"43543.503206018519", "[$-481]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Tū"},
{"43543.503206018519", "[$-481]mmmmmm dd yyyy h:mm AM/PM dddd", "Poutūterangi 19 2019 12:04 p.m. Rātū"},
{"44562.189571759256", "[$-7A]mmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7A]mmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7A]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7A]mmmmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7A]mmm dd yyyy h:mm AM/PM", "Kila 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7A]mmmm dd yyyy h:mm AM/PM aaa", "Kila 19 2019 12:04 PM Kila"},
{"43543.503206018519", "[$-7A]mmmmm dd yyyy h:mm AM/PM ddd", "K 19 2019 12:04 PM Kila"},
{"43543.503206018519", "[$-7A]mmmmmm dd yyyy h:mm AM/PM dddd", "Kila 19 2019 12:04 PM Kila Ante"},
{"44562.189571759256", "[$-47A]mmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47A]mmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47A]mmmmm dd yyyy h:mm AM/PM", "K 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47A]mmmmmm dd yyyy h:mm AM/PM", "Kiñe Tripantu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-47A]mmm dd yyyy h:mm AM/PM", "Kila 19 2019 12:04 PM"},
{"43543.503206018519", "[$-47A]mmmm dd yyyy h:mm AM/PM aaa", "Kila 19 2019 12:04 PM Kila"},
{"43543.503206018519", "[$-47A]mmmmm dd yyyy h:mm AM/PM ddd", "K 19 2019 12:04 PM Kila"},
{"43543.503206018519", "[$-47A]mmmmmm dd yyyy h:mm AM/PM dddd", "Kila 19 2019 12:04 PM Kila Ante"},
{"44562.189571759256", "[$-4E]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947. 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-4E]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-4E]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-4E]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"43543.503206018519", "[$-4E]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-4E]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-4E]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-4E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u0935\u093E\u0930"},
{"44562.189571759256", "[$-44E]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947. 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-44E]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-44E]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E.\u092A\u0942."},
{"44562.189571759256", "[$-44E]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u0947\u0935\u093E\u0930\u0940 01 2022 4:32 \u092E.\u092A\u0942."},
{"43543.503206018519", "[$-44E]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-44E]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-44E]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933."},
{"43543.503206018519", "[$-44E]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902. \u092E\u0902\u0917\u0933\u0935\u093E\u0930"},
{"43543.503206018519", "[$-44E]mmmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-44E]mmmmm dd yyyy h:mm AM/PM", "\u092E 19 2019 12:04 \u092E.\u0928\u0902."},
{"43543.503206018519", "[$-44E]mmmmmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E.\u0928\u0902."},
{"44562.189571759256", "[$-7C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C]mmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C]mmmmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C]mmmm dd yyyy h:mm AM/PM aaa", "Enniskó:Wa 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-7C]mmmmm dd yyyy h:mm AM/PM ddd", "E 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-7C]mmmmmm dd yyyy h:mm AM/PM dddd", "Enniskó:Wa 19 2019 12:04 PM Ratironhia'kehronòn:ke"},
{"44562.189571759256", "[$-47C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47C]mmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47C]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 AM"},
{"44562.189571759256", "[$-47C]mmmmmm dd yyyy h:mm AM/PM", "Tsothohrkó:Wa 01 2022 4:32 AM"},
{"43543.503206018519", "[$-47C]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-47C]mmmm dd yyyy h:mm AM/PM aaa", "Enniskó:Wa 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-47C]mmmmm dd yyyy h:mm AM/PM ddd", "E 19 2019 12:04 PM Tue"},
{"43543.503206018519", "[$-47C]mmmmmm dd yyyy h:mm AM/PM dddd", "Enniskó:Wa 19 2019 12:04 PM Ratironhia'kehronòn:ke"},
{"44562.189571759256", "[$-50]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-50]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-50]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-50]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"43543.503206018519", "[$-50]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."},
{"43543.503206018519", "[$-50]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-50]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-50]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"},
{"44562.189571759256", "[$-7850]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-7850]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-7850]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-7850]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"43543.503206018519", "[$-7850]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."},
{"43543.503206018519", "[$-7850]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-7850]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-7850]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"},
{"44562.189571759256", "[$-450]mmm dd yyyy h:mm AM/PM", "1-р сар 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-450]mmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-450]mmmmm dd yyyy h:mm AM/PM", "\u041D 01 2022 4:32 \u04AF.\u04E9."},
{"44562.189571759256", "[$-450]mmmmmm dd yyyy h:mm AM/PM", "\u041D\u044D\u0433\u0434\u04AF\u0433\u044D\u044D\u0440 \u0441\u0430\u0440 01 2022 4:32 \u04AF.\u04E9."},
{"43543.503206018519", "[$-450]mmm dd yyyy h:mm AM/PM", "3-р сар 19 2019 12:04 \u04AF.\u0445."},
{"43543.503206018519", "[$-450]mmmm dd yyyy h:mm AM/PM aaa", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-450]mmmmm dd yyyy h:mm AM/PM ddd", "\u0413 19 2019 12:04 \u04AF.\u0445. \u041C\u044F"},
{"43543.503206018519", "[$-450]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440 19 2019 12:04 \u04AF.\u0445. \u043C\u044F\u0433\u043C\u0430\u0440"},
{"44562.189571759256", "[$-7C50]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-7C50]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C50]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-7C50]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"},
{"44562.189571759256", "[$-7C50]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828"},
{"44896.18957170139", "[$-7C50]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"},
{"44562.189571759256", "[$-850]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-850]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"},
{"44562.189571759256", "[$-850]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-850]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"},
{"44562.189571759256", "[$-850]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1835\u1822\u1837\u182D\u1824\u182D\u1820\u1828"},
{"44896.18957170139", "[$-850]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182D\u1820\u1837\u1820\u182D\u202F\u1824\u1828 \u1833\u1825\u1837\u182A\u1821\u1828"},
{"44562.189571759256", "[$-C50]mmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-C50]mmm dd yyyy h:mm AM/PM", "M12 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C50]mmmm dd yyyy h:mm AM/PM", "M01 01 2022 4:32 AM"},
{"44896.18957170139", "[$-C50]mmmm dd yyyy h:mm AM/PM aaa", "M12 01 2022 4:32 AM \u182B\u1826\u1837\u182A\u1826"},
{"44562.189571759256", "[$-C50]mmmmm dd yyyy h:mm AM/PM ddd", "M 01 2022 4:32 AM \u182A\u1822\u182E\u182A\u1820"},
{"44896.18957170139", "[$-C50]mmmmm dd yyyy h:mm AM/PM dddd", "M 01 2022 4:32 AM \u182B\u1826\u1837\u182A\u1826"},
{"44562.189571759256", "[$-61]mmm dd yyyy h:mm AM/PM", "\u091C\u0928 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-61]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-61]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-61]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-61]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-61]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-61]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-61]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930"},
{"44562.189571759256", "[$-861]mmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-861]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-861]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-861]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-861]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-861]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-861]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-861]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u092C\u093E\u0930"},
{"44562.189571759256", "[$-461]mmm dd yyyy h:mm AM/PM", "\u091C\u0928 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-461]mmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-461]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"44562.189571759256", "[$-461]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u0928\u0935\u0930\u0940 01 2022 4:32 \u092A\u0942\u0930\u094D\u0935\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-461]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928"},
{"43543.503206018519", "[$-461]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-461]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932"},
{"43543.503206018519", "[$-461]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u0905\u092A\u0930\u093E\u0939\u094D\u0928 \u092E\u0919\u094D\u0917\u0932\u0935\u093E\u0930"},
{"44562.189571759256", "[$-14]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-14]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-14]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-14]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-14]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-14]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir."},
{"43543.503206018519", "[$-14]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir."},
{"43543.503206018519", "[$-14]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"},
{"44562.189571759256", "[$-7C14]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-7C14]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-7C14]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-7C14]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-7C14]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-7C14]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir"},
{"43543.503206018519", "[$-7C14]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir"},
{"43543.503206018519", "[$-7C14]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"},
{"44562.189571759256", "[$-414]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-414]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-414]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-414]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-414]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-414]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 p.m. tir"},
{"43543.503206018519", "[$-414]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. tir"},
{"43543.503206018519", "[$-414]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 p.m. tirsdag"},
{"44562.189571759256", "[$-7814]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-7814]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-7814]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-7814]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."},
{"43543.503206018519", "[$-7814]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 e.m."},
{"43543.503206018519", "[$-7814]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.m. tys"},
{"43543.503206018519", "[$-7814]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.m. tys"},
{"43543.503206018519", "[$-7814]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.m. tysdag"},
{"44562.189571759256", "[$-814]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-814]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-814]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 f.m."},
{"44562.189571759256", "[$-814]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 f.m."},
{"43543.503206018519", "[$-814]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 e.m."},
{"43543.503206018519", "[$-814]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 e.m. tys"},
{"43543.503206018519", "[$-814]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 e.m. tys"},
{"43543.503206018519", "[$-814]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 e.m. tysdag"},
{"44562.189571759256", "[$-82]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82]mmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44562.189571759256", "[$-82]mmmmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"},
{"43543.503206018519", "[$-82]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 PM"},
{"43543.503206018519", "[$-82]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 PM dma."},
{"43543.503206018519", "[$-82]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM dma."},
{"43543.503206018519", "[$-82]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 PM dimarts"},
{"44562.189571759256", "[$-482]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-482]mmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"},
{"44562.189571759256", "[$-482]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 AM"},
{"44562.189571759256", "[$-482]mmmmmm dd yyyy h:mm AM/PM", "genièr 01 2022 4:32 AM"},
{"43543.503206018519", "[$-482]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 PM"},
{"43543.503206018519", "[$-482]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 PM dma."},
{"43543.503206018519", "[$-482]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM dma."},
{"43543.503206018519", "[$-482]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 PM dimarts"},
{"44562.189571759256", "[$-48]mmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"44562.189571759256", "[$-48]mmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"44562.189571759256", "[$-48]mmmmm dd yyyy h:mm AM/PM", "\u0B1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-48]mmmmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"43543.503206018519", "[$-48]mmm dd yyyy h:mm AM/PM", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM"},
{"43543.503206018519", "[$-48]mmmm dd yyyy h:mm AM/PM aaa", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."},
{"43543.503206018519", "[$-48]mmmmm dd yyyy h:mm AM/PM ddd", "\u0B2E 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."},
{"43543.503206018519", "[$-48]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30"},
{"44562.189571759256", "[$-448]mmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"44562.189571759256", "[$-448]mmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"44562.189571759256", "[$-448]mmmmm dd yyyy h:mm AM/PM", "\u0B1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-448]mmmmmm dd yyyy h:mm AM/PM", "\u0B1C\u0B3E\u0B28\u0B41\u0B5F\u0B3E\u0B30\u0B40 01 2022 4:32 AM"},
{"43543.503206018519", "[$-448]mmm dd yyyy h:mm AM/PM", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM"},
{"43543.503206018519", "[$-448]mmmm dd yyyy h:mm AM/PM aaa", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."},
{"43543.503206018519", "[$-448]mmmmm dd yyyy h:mm AM/PM ddd", "\u0B2E 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33."},
{"43543.503206018519", "[$-448]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0B2E\u0B3E\u0B30\u0B4D\u0B1A\u0B4D\u0B1A 19 2019 12:04 PM \u0B2E\u0B19\u0B4D\u0B17\u0B33\u0B2C\u0B3E\u0B30"},
{"44562.189571759256", "[$-72]mmm dd yyyy h:mm AM/PM", "Ama 01 2022 4:32 WD"},
{"44562.189571759256", "[$-72]mmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"},
{"44562.189571759256", "[$-72]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 WD"},
{"44562.189571759256", "[$-72]mmmmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"},
{"43543.503206018519", "[$-72]mmm dd yyyy h:mm AM/PM", "Bit 19 2019 12:04 WB"},
{"43543.503206018519", "[$-72]mmmm dd yyyy h:mm AM/PM aaa", "Bitooteessa 19 2019 12:04 WB Qib"},
{"43543.503206018519", "[$-72]mmmmm dd yyyy h:mm AM/PM ddd", "B 19 2019 12:04 WB Qib"},
{"43543.503206018519", "[$-72]mmmmmm dd yyyy h:mm AM/PM dddd", "Bitooteessa 19 2019 12:04 WB Qibxata"},
{"44562.189571759256", "[$-472]mmm dd yyyy h:mm AM/PM", "Ama 01 2022 4:32 WD"},
{"44562.189571759256", "[$-472]mmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"},
{"44562.189571759256", "[$-472]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 WD"},
{"44562.189571759256", "[$-472]mmmmmm dd yyyy h:mm AM/PM", "Amajjii 01 2022 4:32 WD"},
{"43543.503206018519", "[$-472]mmm dd yyyy h:mm AM/PM", "Bit 19 2019 12:04 WB"},
{"43543.503206018519", "[$-472]mmmm dd yyyy h:mm AM/PM aaa", "Bitooteessa 19 2019 12:04 WB Qib"},
{"43543.503206018519", "[$-472]mmmmm dd yyyy h:mm AM/PM ddd", "B 19 2019 12:04 WB Qib"},
{"43543.503206018519", "[$-472]mmmmmm dd yyyy h:mm AM/PM dddd", "Bitooteessa 19 2019 12:04 WB Qibxata"},
{"44562.189571759256", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-63]mmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-63]mmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-63]mmmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-63]mmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-63]mmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-63]mmmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"43543.503206018519", "[$-63]mmm dd yyyy h:mm AM/PM", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648."},
{"43543.503206018519", "[$-63]mmmm dd yyyy h:mm AM/PM aaa", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"43543.503206018519", "[$-63]mmmmm dd yyyy h:mm AM/PM ddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"43543.503206018519", "[$-63]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"44562.189571759256", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-463]mmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-463]mmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44562.189571759256", "[$-463]mmmmmm dd yyyy h:mm AM/PM", "\u0633\u0644\u0648\u0627\u063A\u0647 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-463]mmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-463]mmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"44713.188888888886", "[$-463]mmmmmm dd yyyy h:mm AM/PM", "\u0686\u0646\u06AB\u0627 \u069A\u0632\u0645\u0631\u0649 01 2022 4:32 \u063A.\u0645."},
{"43543.503206018519", "[$-463]mmm dd yyyy h:mm AM/PM", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648."},
{"43543.503206018519", "[$-463]mmmm dd yyyy h:mm AM/PM aaa", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"43543.503206018519", "[$-463]mmmmm dd yyyy h:mm AM/PM ddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"43543.503206018519", "[$-463]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0648\u0631\u0649 19 2019 12:04 \u063A.\u0648. \u062F\u0631\u06D0\u0646\u06CD"},
{"44562.189571759256", "[$-29]mmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-29]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-29]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-29]mmmmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"43543.503206018519", "[$-29]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638"},
{"43543.503206018519", "[$-29]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"43543.503206018519", "[$-29]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"43543.503206018519", "[$-29]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"44562.189571759256", "[$-429]mmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-429]mmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-429]mmmmm dd yyyy h:mm AM/PM", "\u0698 01 2022 4:32 \u0642.\u0638"},
{"44562.189571759256", "[$-429]mmmmmm dd yyyy h:mm AM/PM", "\u0698\u0627\u0646\u0648\u064A\u0647 01 2022 4:32 \u0642.\u0638"},
{"43543.503206018519", "[$-429]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638"},
{"43543.503206018519", "[$-429]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"43543.503206018519", "[$-429]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"43543.503206018519", "[$-429]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0628.\u0638 \u0633\u0647 \u0634\u0646\u0628\u0647"},
{"44562.189571759256", "[$-15]mmm dd yyyy h:mm AM/PM", "sty 01 2022 4:32 AM"},
{"44562.189571759256", "[$-15]mmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"},
{"44562.189571759256", "[$-15]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-15]mmmmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"},
{"43543.503206018519", "[$-15]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-15]mmmm dd yyyy h:mm AM/PM aaa", "marzec 19 2019 12:04 PM wt."},
{"43543.503206018519", "[$-15]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wt."},
{"43543.503206018519", "[$-15]mmmmmm dd yyyy h:mm AM/PM dddd", "marzec 19 2019 12:04 PM wtorek"},
{"44562.189571759256", "[$-415]mmm dd yyyy h:mm AM/PM", "sty 01 2022 4:32 AM"},
{"44562.189571759256", "[$-415]mmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"},
{"44562.189571759256", "[$-415]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-415]mmmmmm dd yyyy h:mm AM/PM", "styczeń 01 2022 4:32 AM"},
{"43543.503206018519", "[$-415]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-415]mmmm dd yyyy h:mm AM/PM aaa", "marzec 19 2019 12:04 PM wt."},
{"43543.503206018519", "[$-415]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM wt."},
{"43543.503206018519", "[$-415]mmmmmm dd yyyy h:mm AM/PM dddd", "marzec 19 2019 12:04 PM wtorek"},
{"44562.189571759256", "[$-16]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-16]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-16]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-16]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"43543.503206018519", "[$-16]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-16]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-16]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-16]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"},
{"44562.189571759256", "[$-416]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-416]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-416]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-416]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"43543.503206018519", "[$-416]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-416]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-416]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-416]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"},
{"44562.189571759256", "[$-816]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-816]mmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"44562.189571759256", "[$-816]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-816]mmmmmm dd yyyy h:mm AM/PM", "janeiro 01 2022 4:32 AM"},
{"43543.503206018519", "[$-816]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-816]mmmm dd yyyy h:mm AM/PM aaa", "março 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-816]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ter"},
{"43543.503206018519", "[$-816]mmmmmm dd yyyy h:mm AM/PM dddd", "março 19 2019 12:04 PM terça-feira"},
{"44562.189571759256", "[$-46]mmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-46]mmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-46]mmmmm dd yyyy h:mm AM/PM", "\u0A1C 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-46]mmmmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"43543.503206018519", "[$-46]mmm dd yyyy h:mm AM/PM", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E"},
{"43543.503206018519", "[$-46]mmmm dd yyyy h:mm AM/PM aaa", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."},
{"43543.503206018519", "[$-46]mmmmm dd yyyy h:mm AM/PM ddd", "\u0A2E 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."},
{"43543.503206018519", "[$-46]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30"},
{"44562.189571759256", "[$-7C46]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C46]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C46]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C46]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C46]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C46]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"43543.503206018519", "[$-7C46]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"43543.503206018519", "[$-7C46]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"44562.189571759256", "[$-446]mmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-446]mmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-446]mmmmm dd yyyy h:mm AM/PM", "\u0A1C 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"44562.189571759256", "[$-446]mmmmmm dd yyyy h:mm AM/PM", "\u0A1C\u0A28\u0A35\u0A30\u0A40 01 2022 4:32 \u0A38\u0A35\u0A47\u0A30"},
{"43543.503206018519", "[$-446]mmm dd yyyy h:mm AM/PM", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E"},
{"43543.503206018519", "[$-446]mmmm dd yyyy h:mm AM/PM aaa", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."},
{"43543.503206018519", "[$-446]mmmmm dd yyyy h:mm AM/PM ddd", "\u0A2E 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32."},
{"43543.503206018519", "[$-446]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0A2E\u0A3E\u0A30\u0A1A 19 2019 12:04 \u0A38\u0A3C\u0A3E\u0A2E \u0A2E\u0A70\u0A17\u0A32\u0A35\u0A3E\u0A30"},
{"44562.189571759256", "[$-846]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-846]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-846]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-846]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-846]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-846]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"43543.503206018519", "[$-846]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"43543.503206018519", "[$-846]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0628\u062F\u06BE"},
{"44562.189571759256", "[$-6B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-6B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-6B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-6B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-6B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-6B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. ati"},
{"43543.503206018519", "[$-6B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. ati"},
{"43543.503206018519", "[$-6B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. atipachaw"},
{"44562.189571759256", "[$-46B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-46B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-46B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-46B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-46B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-46B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. ati"},
{"43543.503206018519", "[$-46B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. ati"},
{"43543.503206018519", "[$-46B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. atipachaw"},
{"44562.189571759256", "[$-86B]mmm dd yyyy h:mm AM/PM", "kull 01 2022 4:32 AM"},
{"44562.189571759256", "[$-86B]mmmm dd yyyy h:mm AM/PM", "kulla 01 2022 4:32 AM"},
{"44562.189571759256", "[$-86B]mmmmm dd yyyy h:mm AM/PM", "k 01 2022 4:32 AM"},
{"44562.189571759256", "[$-86B]mmmmmm dd yyyy h:mm AM/PM", "kulla 01 2022 4:32 AM"},
{"43543.503206018519", "[$-86B]mmm dd yyyy h:mm AM/PM", "paw 19 2019 12:04 PM"},
{"43543.503206018519", "[$-86B]mmmm dd yyyy h:mm AM/PM aaa", "pawkar 19 2019 12:04 PM wan"},
{"43543.503206018519", "[$-86B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM wan"},
{"43543.503206018519", "[$-86B]mmmmmm dd yyyy h:mm AM/PM dddd", "pawkar 19 2019 12:04 PM wanra"},
{"44562.189571759256", "[$-C6B]mmm dd yyyy h:mm AM/PM", "Qul 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-C6B]mmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-C6B]mmmmm dd yyyy h:mm AM/PM", "Q 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-C6B]mmmmmm dd yyyy h:mm AM/PM", "Qulla puquy 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-C6B]mmm dd yyyy h:mm AM/PM", "Pau 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-C6B]mmmm dd yyyy h:mm AM/PM aaa", "Pauqar waray 19 2019 12:04 p.m. Mar"},
{"43543.503206018519", "[$-C6B]mmmmm dd yyyy h:mm AM/PM ddd", "P 19 2019 12:04 p.m. Mar"},
{"43543.503206018519", "[$-C6B]mmmmmm dd yyyy h:mm AM/PM dddd", "Pauqar waray 19 2019 12:04 p.m. Martes"},
{"44562.189571759256", "[$-18]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-18]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-18]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-18]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-18]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-18]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-18]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-18]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"},
{"44562.189571759256", "[$-818]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-818]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-818]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-818]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-818]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-818]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. Mar"},
{"43543.503206018519", "[$-818]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. Mar"},
{"43543.503206018519", "[$-818]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"},
{"44562.189571759256", "[$-418]mmm dd yyyy h:mm AM/PM", "ian. 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-418]mmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-418]mmmmm dd yyyy h:mm AM/PM", "i 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-418]mmmmmm dd yyyy h:mm AM/PM", "ianuarie 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-418]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 p.m."},
{"43543.503206018519", "[$-418]mmmm dd yyyy h:mm AM/PM aaa", "martie 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-418]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-418]mmmmmm dd yyyy h:mm AM/PM dddd", "martie 19 2019 12:04 p.m. marți"},
{"44562.189571759256", "[$-17]mmm dd yyyy h:mm AM/PM", "schan. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-17]mmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"},
{"44562.189571759256", "[$-17]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-17]mmmmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"},
{"43543.503206018519", "[$-17]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"},
{"43543.503206018519", "[$-17]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM ma"},
{"43543.503206018519", "[$-17]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ma"},
{"43543.503206018519", "[$-17]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-417]mmm dd yyyy h:mm AM/PM", "schan. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-417]mmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"},
{"44562.189571759256", "[$-417]mmmmm dd yyyy h:mm AM/PM", "s 01 2022 4:32 AM"},
{"44562.189571759256", "[$-417]mmmmmm dd yyyy h:mm AM/PM", "schaner 01 2022 4:32 AM"},
{"43543.503206018519", "[$-417]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"},
{"43543.503206018519", "[$-417]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM ma"},
{"43543.503206018519", "[$-417]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ma"},
{"43543.503206018519", "[$-417]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM mardi"},
{"44562.189571759256", "[$-19]mmm dd yyyy h:mm AM/PM", "янв. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-19]mmmm dd yyyy h:mm AM/PM", "январь 01 2022 4:32 AM"},
{"44562.189571759256", "[$-19]mmmmm dd yyyy h:mm AM/PM", "я 01 2022 4:32 AM"},
{"43543.503206018519", "[$-19]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-19]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-19]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"43543.503206018519", "[$-819]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-819]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-819]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"43543.503206018519", "[$-419]mmm dd yyyy h:mm AM/PM aaa", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-419]mmmm dd yyyy h:mm AM/PM ddd", "март 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-419]mmmmm dd yyyy h:mm AM/PM dddd", "м 19 2019 12:04 PM \u0432\u0442\u043E\u0440\u043D\u0438\u043A"},
{"44562.189571759256", "[$-85]mmm dd yyyy h:mm AM/PM", "\u0422\u0445\u0441 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-85]mmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-85]mmmmm dd yyyy h:mm AM/PM", "\u0422 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-85]mmmmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"},
{"43543.503206018519", "[$-85]mmm dd yyyy h:mm AM/PM", "\u041A\u043B\u043D 19 2019 12:04 \u041A\u041A"},
{"43543.503206018519", "[$-85]mmmm dd yyyy h:mm AM/PM aaa", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u043E\u043F"},
{"43543.503206018519", "[$-85]mmmmm dd yyyy h:mm AM/PM ddd", "\u041A 19 2019 12:04 \u041A\u041A \u043E\u043F"},
{"43543.503206018519", "[$-85]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A"},
{"44562.189571759256", "[$-485]mmm dd yyyy h:mm AM/PM", "\u0422\u0445\u0441 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-485]mmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-485]mmmmm dd yyyy h:mm AM/PM", "\u0422 01 2022 4:32 \u041A\u0418"},
{"44562.189571759256", "[$-485]mmmmmm dd yyyy h:mm AM/PM", "\u0422\u043E\u0445\u0441\u0443\u043D\u043D\u044C\u0443 01 2022 4:32 \u041A\u0418"},
{"43543.503206018519", "[$-485]mmm dd yyyy h:mm AM/PM", "\u041A\u043B\u043D 19 2019 12:04 \u041A\u041A"},
{"43543.503206018519", "[$-485]mmmm dd yyyy h:mm AM/PM aaa", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u043E\u043F"},
{"43543.503206018519", "[$-485]mmmmm dd yyyy h:mm AM/PM ddd", "\u041A 19 2019 12:04 \u041A\u041A \u043E\u043F"},
{"43543.503206018519", "[$-485]mmmmmm dd yyyy h:mm AM/PM dddd", "\u041A\u0443\u043B\u0443\u043D \u0442\u0443\u0442\u0430\u0440 19 2019 12:04 \u041A\u041A \u041E\u043F\u0442\u0443\u043E\u0440\u0443\u043D\u043D\u044C\u0443\u043A"},
{"44562.189571759256", "[$-703B]mmm dd yyyy h:mm AM/PM", "uđiv 01 2022 4:32 AM"},
{"44562.189571759256", "[$-703B]mmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-703B]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44562.189571759256", "[$-703B]mmmmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-703B]mmm dd yyyy h:mm AM/PM", "njuh 19 2019 12:04 PM"},
{"43543.503206018519", "[$-703B]mmmm dd yyyy h:mm AM/PM aaa", "njuhčâmáánu 19 2019 12:04 PM maj"},
{"43543.503206018519", "[$-703B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM maj"},
{"43543.503206018519", "[$-703B]mmmmmm dd yyyy h:mm AM/PM dddd", "njuhčâmáánu 19 2019 12:04 PM majebargâ"},
{"44562.189571759256", "[$-243B]mmm dd yyyy h:mm AM/PM", "uđiv 01 2022 4:32 AM"},
{"44562.189571759256", "[$-243B]mmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-243B]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44562.189571759256", "[$-243B]mmmmmm dd yyyy h:mm AM/PM", "uđđâivemáánu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-243B]mmm dd yyyy h:mm AM/PM", "njuh 19 2019 12:04 PM"},
{"43543.503206018519", "[$-243B]mmmm dd yyyy h:mm AM/PM aaa", "njuhčâmáánu 19 2019 12:04 PM maj"},
{"43543.503206018519", "[$-243B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM maj"},
{"43543.503206018519", "[$-243B]mmmmmm dd yyyy h:mm AM/PM dddd", "njuhčâmáánu 19 2019 12:04 PM majebargâ"},
{"44562.189571759256", "[$-7C3B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C3B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C3B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C3B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C3B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C3B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-7C3B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-7C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"},
{"44562.189571759256", "[$-103B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"},
{"44562.189571759256", "[$-103B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"44562.189571759256", "[$-103B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"},
{"44562.189571759256", "[$-103B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"43543.503206018519", "[$-103B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"},
{"43543.503206018519", "[$-103B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-103B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-103B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"},
{"44562.189571759256", "[$-143B]mmm dd yyyy h:mm AM/PM", "ådåj 01 2022 4:32 AM"},
{"44562.189571759256", "[$-143B]mmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"44562.189571759256", "[$-143B]mmmmm dd yyyy h:mm AM/PM", "å 01 2022 4:32 AM"},
{"44562.189571759256", "[$-143B]mmmmmm dd yyyy h:mm AM/PM", "ådåjakmánno 01 2022 4:32 AM"},
{"43543.503206018519", "[$-143B]mmm dd yyyy h:mm AM/PM", "snju 19 2019 12:04 PM"},
{"43543.503206018519", "[$-143B]mmmm dd yyyy h:mm AM/PM aaa", "sjnjuktjamánno 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-143B]mmmmm dd yyyy h:mm AM/PM ddd", "s 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-143B]mmmmmm dd yyyy h:mm AM/PM dddd", "sjnjuktjamánno 19 2019 12:04 PM dijstahka"},
{"44562.189571759256", "[$-3B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-3B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-3B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-3B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."},
{"43543.503206018519", "[$-3B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 e.b."},
{"43543.503206018519", "[$-3B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 e.b. maŋ"},
{"43543.503206018519", "[$-3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 e.b. maŋ"},
{"43543.503206018519", "[$-3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 e.b. maŋŋebárga"},
{"44562.189571759256", "[$-C3B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C3B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C3B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C3B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-C3B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 PM"},
{"43543.503206018519", "[$-C3B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 PM di"},
{"43543.503206018519", "[$-C3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM di"},
{"43543.503206018519", "[$-C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 PM maŋŋebárga"},
{"44562.189571759256", "[$-43B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-43B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-43B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 i.b."},
{"44562.189571759256", "[$-43B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 i.b."},
{"43543.503206018519", "[$-43B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 e.b."},
{"43543.503206018519", "[$-43B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 e.b. maŋ"},
{"43543.503206018519", "[$-43B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 e.b. maŋ"},
{"43543.503206018519", "[$-43B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 e.b. maŋŋebárga"},
{"44562.189571759256", "[$-83B]mmm dd yyyy h:mm AM/PM", "ođđj 01 2022 4:32 AM"},
{"44562.189571759256", "[$-83B]mmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 AM"},
{"44562.189571759256", "[$-83B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44562.189571759256", "[$-83B]mmmmmm dd yyyy h:mm AM/PM", "ođđajagemánnu 01 2022 4:32 AM"},
{"43543.503206018519", "[$-83B]mmm dd yyyy h:mm AM/PM", "njuk 19 2019 12:04 PM"},
{"43543.503206018519", "[$-83B]mmmm dd yyyy h:mm AM/PM aaa", "njukčamánnu 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-83B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dis"},
{"43543.503206018519", "[$-83B]mmmmmm dd yyyy h:mm AM/PM dddd", "njukčamánnu 19 2019 12:04 PM disdat"},
{"44562.189571759256", "[$-743B]mmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"44562.189571759256", "[$-743B]mmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"44562.189571759256", "[$-743B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44562.189571759256", "[$-743B]mmmmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"43543.503206018519", "[$-743B]mmm dd yyyy h:mm AM/PM", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM"},
{"43543.503206018519", "[$-743B]mmmm dd yyyy h:mm AM/PM aaa", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mâ"},
{"43543.503206018519", "[$-743B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM mâ"},
{"43543.503206018519", "[$-743B]mmmmmm dd yyyy h:mm AM/PM dddd", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mââibargg"},
{"44562.189571759256", "[$-203B]mmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"44562.189571759256", "[$-203B]mmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"44562.189571759256", "[$-203B]mmmmm dd yyyy h:mm AM/PM", "o 01 2022 4:32 AM"},
{"44562.189571759256", "[$-203B]mmmmmm dd yyyy h:mm AM/PM", "ođđee\u00B4jjmään 01 2022 4:32 AM"},
{"43543.503206018519", "[$-203B]mmm dd yyyy h:mm AM/PM", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM"},
{"43543.503206018519", "[$-203B]mmmm dd yyyy h:mm AM/PM aaa", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mâ"},
{"43543.503206018519", "[$-203B]mmmmm dd yyyy h:mm AM/PM ddd", "p 19 2019 12:04 PM mâ"},
{"43543.503206018519", "[$-203B]mmmmmm dd yyyy h:mm AM/PM dddd", "pâ\u00B4zzlâšttam-mään 19 2019 12:04 PM mââibargg"},
{"44562.189571759256", "[$-783B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-783B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"44562.189571759256", "[$-783B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44562.189571759256", "[$-783B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"43543.503206018519", "[$-783B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"},
{"43543.503206018519", "[$-783B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-783B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-783B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"},
{"44562.189571759256", "[$-183B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-183B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"44562.189571759256", "[$-183B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44562.189571759256", "[$-183B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"43543.503206018519", "[$-183B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"},
{"43543.503206018519", "[$-183B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-183B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-183B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"},
{"44562.189571759256", "[$-1C3B]mmm dd yyyy h:mm AM/PM", "tsïen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C3B]mmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C3B]mmmmm dd yyyy h:mm AM/PM", "t 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C3B]mmmmmm dd yyyy h:mm AM/PM", "tsïengele 01 2022 4:32 AM"},
{"43543.503206018519", "[$-1C3B]mmm dd yyyy h:mm AM/PM", "njok 19 2019 12:04 PM"},
{"43543.503206018519", "[$-1C3B]mmmm dd yyyy h:mm AM/PM aaa", "njoktje 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-1C3B]mmmmm dd yyyy h:mm AM/PM ddd", "n 19 2019 12:04 PM dæj"},
{"43543.503206018519", "[$-1C3B]mmmmmm dd yyyy h:mm AM/PM dddd", "njoktje 19 2019 12:04 PM dæjsta"},
{"44562.189571759256", "[$-4F]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-4F]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-4F]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-4F]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"43543.503206018519", "[$-4F]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924"},
{"43543.503206018519", "[$-4F]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"},
{"43543.503206018519", "[$-4F]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"},
{"43543.503206018519", "[$-4F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903"},
{"44562.189571759256", "[$-44F]mmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-44F]mmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-44F]mmmmm dd yyyy h:mm AM/PM", "\u091C 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"44562.189571759256", "[$-44F]mmmmmm dd yyyy h:mm AM/PM", "\u091C\u093E\u0928\u094D\u092F\u0941\u0905\u0930\u0940 01 2022 4:32 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u0942\u0930\u094D\u0935"},
{"43543.503206018519", "[$-44F]mmm dd yyyy h:mm AM/PM", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924"},
{"43543.503206018519", "[$-44F]mmmm dd yyyy h:mm AM/PM aaa", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"},
{"43543.503206018519", "[$-44F]mmmmm dd yyyy h:mm AM/PM ddd", "\u092E 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0919\u094D\u0917"},
{"43543.503206018519", "[$-44F]mmmmmm dd yyyy h:mm AM/PM dddd", "\u092E\u093E\u0930\u094D\u091A 19 2019 12:04 \u092E\u0927\u094D\u092F\u093E\u0928\u092A\u091A\u094D\u092F\u093E\u0924 \u092E\u0902\u0917\u0932\u0935\u093E\u0938\u0930\u0903"},
{"44562.189571759256", "[$-91]mmm dd yyyy h:mm AM/PM", "Faoi 01 2022 4:32 m"},
{"44562.189571759256", "[$-91]mmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"},
{"44562.189571759256", "[$-91]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 m"},
{"44562.189571759256", "[$-91]mmmmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"},
{"43543.503206018519", "[$-91]mmm dd yyyy h:mm AM/PM", "Màrt 19 2019 12:04 f"},
{"43543.503206018519", "[$-91]mmmm dd yyyy h:mm AM/PM aaa", "Am Màrt 19 2019 12:04 f DiM"},
{"43543.503206018519", "[$-91]mmmmm dd yyyy h:mm AM/PM ddd", "A 19 2019 12:04 f DiM"},
{"43543.503206018519", "[$-91]mmmmmm dd yyyy h:mm AM/PM dddd", "Am Màrt 19 2019 12:04 f DiMàirt"},
{"44562.189571759256", "[$-491]mmm dd yyyy h:mm AM/PM", "Faoi 01 2022 4:32 m"},
{"44562.189571759256", "[$-491]mmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"},
{"44562.189571759256", "[$-491]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 m"},
{"44562.189571759256", "[$-491]mmmmmm dd yyyy h:mm AM/PM", "Am Faoilleach 01 2022 4:32 m"},
{"43543.503206018519", "[$-491]mmm dd yyyy h:mm AM/PM", "Màrt 19 2019 12:04 f"},
{"43543.503206018519", "[$-491]mmmm dd yyyy h:mm AM/PM aaa", "Am Màrt 19 2019 12:04 f DiM"},
{"43543.503206018519", "[$-491]mmmmm dd yyyy h:mm AM/PM ddd", "A 19 2019 12:04 f DiM"},
{"43543.503206018519", "[$-491]mmmmmm dd yyyy h:mm AM/PM dddd", "Am Màrt 19 2019 12:04 f DiMàirt"},
{"44562.189571759256", "[$-6C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-6C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"},
{"43543.503206018519", "[$-6C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-6C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-6C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-1C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-1C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-1C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-1C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-1C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-301A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-301A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-301A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-301A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-301A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"},
{"43543.503206018519", "[$-301A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-301A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442\u043E"},
{"43543.503206018519", "[$-301A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-281A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-281A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-281A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-281A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-281A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"},
{"43543.503206018519", "[$-281A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-281A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-281A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-C1A]mmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C1A]mmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C1A]mmmmm dd yyyy h:mm AM/PM", "\u0458 01 2022 4:32 AM"},
{"44562.189571759256", "[$-C1A]mmmmmm dd yyyy h:mm AM/PM", "\u0458\u0430\u043D\u0443\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-C1A]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM"},
{"43543.503206018519", "[$-C1A]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-C1A]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0443\u0442."},
{"43543.503206018519", "[$-C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0443\u0442\u043E\u0440\u0430\u043A"},
{"44562.189571759256", "[$-701A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-701A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-701A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-701A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"43543.503206018519", "[$-701A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"},
{"43543.503206018519", "[$-701A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-701A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-701A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"},
{"44562.189571759256", "[$-7C1A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-7C1A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-7C1A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-7C1A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"43543.503206018519", "[$-7C1A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"},
{"43543.503206018519", "[$-7C1A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-7C1A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-7C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"},
{"44562.189571759256", "[$-181A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-181A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-181A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-181A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"},
{"43543.503206018519", "[$-181A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"},
{"43543.503206018519", "[$-181A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-181A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-181A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"},
{"44562.189571759256", "[$-2C1A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-2C1A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-2C1A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 prije podne"},
{"44562.189571759256", "[$-2C1A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 prije podne"},
{"43543.503206018519", "[$-2C1A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"},
{"43543.503206018519", "[$-2C1A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-2C1A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-2C1A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"},
{"44562.189571759256", "[$-241A]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-241A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-241A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 pre podne"},
{"44562.189571759256", "[$-241A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 pre podne"},
{"43543.503206018519", "[$-241A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 po podne"},
{"43543.503206018519", "[$-241A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-241A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 po podne uto"},
{"43543.503206018519", "[$-241A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 po podne utorak"},
{"44562.189571759256", "[$-81A]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-81A]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-81A]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-81A]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-81A]mmm dd yyyy h:mm AM/PM", "mart 19 2019 12:04 PM"},
{"43543.503206018519", "[$-81A]mmmm dd yyyy h:mm AM/PM aaa", "mart 19 2019 12:04 PM uto."},
{"43543.503206018519", "[$-81A]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM uto."},
{"43543.503206018519", "[$-81A]mmmmmm dd yyyy h:mm AM/PM dddd", "mart 19 2019 12:04 PM utorak"},
{"44562.189571759256", "[$-6C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C]mmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-6C]mmmmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"},
{"43543.503206018519", "[$-6C]mmm dd yyyy h:mm AM/PM", "Matš 19 2019 12:04 PM"},
{"43543.503206018519", "[$-6C]mmmm dd yyyy h:mm AM/PM aaa", "Matšhe 19 2019 12:04 PM Lbb"},
{"43543.503206018519", "[$-6C]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lbb"},
{"43543.503206018519", "[$-6C]mmmmmm dd yyyy h:mm AM/PM dddd", "Matšhe 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-46C]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46C]mmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46C]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44562.189571759256", "[$-46C]mmmmmm dd yyyy h:mm AM/PM", "Janaware 01 2022 4:32 AM"},
{"43543.503206018519", "[$-46C]mmm dd yyyy h:mm AM/PM", "Matš 19 2019 12:04 PM"},
{"43543.503206018519", "[$-46C]mmmm dd yyyy h:mm AM/PM aaa", "Matšhe 19 2019 12:04 PM Lbb"},
{"43543.503206018519", "[$-46C]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lbb"},
{"43543.503206018519", "[$-46C]mmmmmm dd yyyy h:mm AM/PM dddd", "Matšhe 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-32]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-32]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"44562.189571759256", "[$-32]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-32]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"43543.503206018519", "[$-32]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-32]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-32]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-32]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-832]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-832]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"44562.189571759256", "[$-832]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-832]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"43543.503206018519", "[$-832]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-832]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-832]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-832]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-432]mmm dd yyyy h:mm AM/PM", "Fer. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-432]mmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"44562.189571759256", "[$-432]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-432]mmmmmm dd yyyy h:mm AM/PM", "Ferikgong 01 2022 4:32 AM"},
{"43543.503206018519", "[$-432]mmm dd yyyy h:mm AM/PM", "Mop. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-432]mmmm dd yyyy h:mm AM/PM aaa", "Mopitlwe 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-432]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Lab."},
{"43543.503206018519", "[$-432]mmmmmm dd yyyy h:mm AM/PM dddd", "Mopitlwe 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-59]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-59]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-59]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-59]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"43543.503206018519", "[$-59]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-59]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-59]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-59]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"},
{"44562.189571759256", "[$-7C59]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C59]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C59]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C59]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C59]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C59]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-7C59]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-7C59]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"},
{"44562.189571759256", "[$-859]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-859]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"44562.189571759256", "[$-859]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-859]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u064A 01 2022 4:32 AM"},
{"43543.503206018519", "[$-859]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-859]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-859]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0627\u0631"},
{"43543.503206018519", "[$-859]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0627\u0631\u0628\u0639"},
{"44562.189571759256", "[$-5B]mmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1. 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-5B]mmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-5B]mmmmm dd yyyy h:mm AM/PM", "\u0DA2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-5B]mmmmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"43543.503206018519", "[$-5B]mmm dd yyyy h:mm AM/PM", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4. 19 2019 12:04 \u0DB4.\u0DC0."},
{"43543.503206018519", "[$-5B]mmmm dd yyyy h:mm AM/PM aaa", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"},
{"43543.503206018519", "[$-5B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0DB8 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"},
{"43543.503206018519", "[$-5B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631\u0628\u0639"},
{"44562.189571759256", "[$-45B]mmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1. 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-45B]mmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-45B]mmmmm dd yyyy h:mm AM/PM", "\u0DA2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"44562.189571759256", "[$-45B]mmmmmm dd yyyy h:mm AM/PM", "\u0DA2\u0DB1\u0DC0\u0DCF\u0DBB\u0DD2 01 2022 4:32 \u0DB4\u0DD9.\u0DC0."},
{"43543.503206018519", "[$-45B]mmm dd yyyy h:mm AM/PM", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4. 19 2019 12:04 \u0DB4.\u0DC0."},
{"43543.503206018519", "[$-45B]mmmm dd yyyy h:mm AM/PM aaa", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"},
{"43543.503206018519", "[$-45B]mmmmm dd yyyy h:mm AM/PM ddd", "\u0DB8 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631"},
{"43543.503206018519", "[$-45B]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0DB8\u0DCF\u0DBB\u0DCA\u0DAD\u0DD4 19 2019 12:04 \u0DB4.\u0DC0. \u0627\u0631\u0628\u0639"},
{"44562.189571759256", "[$-1B]mmm dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1B]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1B]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1B]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"},
{"43543.503206018519", "[$-1B]mmm dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"},
{"43543.503206018519", "[$-1B]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 PM ut"},
{"43543.503206018519", "[$-1B]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ut"},
{"43543.503206018519", "[$-1B]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 PM utorok"},
{"44562.189571759256", "[$-41B]mmm dd yyyy h:mm AM/PM", "1 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41B]mmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41B]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41B]mmmmmm dd yyyy h:mm AM/PM", "január 01 2022 4:32 AM"},
{"43543.503206018519", "[$-41B]mmm dd yyyy h:mm AM/PM", "3 19 2019 12:04 PM"},
{"43543.503206018519", "[$-41B]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 PM ut"},
{"43543.503206018519", "[$-41B]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM ut"},
{"43543.503206018519", "[$-41B]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 PM utorok"},
{"44562.189571759256", "[$-24]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 dop."},
{"44562.189571759256", "[$-24]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."},
{"44562.189571759256", "[$-24]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dop."},
{"44562.189571759256", "[$-24]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."},
{"43543.503206018519", "[$-24]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 pop."},
{"43543.503206018519", "[$-24]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 pop. tor."},
{"43543.503206018519", "[$-24]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pop. tor."},
{"43543.503206018519", "[$-24]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 pop. torek"},
{"44562.189571759256", "[$-424]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 dop."},
{"44562.189571759256", "[$-424]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."},
{"44562.189571759256", "[$-424]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dop."},
{"44562.189571759256", "[$-424]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dop."},
{"43543.503206018519", "[$-424]mmm dd yyyy h:mm AM/PM", "mar. 19 2019 12:04 pop."},
{"43543.503206018519", "[$-424]mmmm dd yyyy h:mm AM/PM aaa", "marec 19 2019 12:04 pop. tor."},
{"43543.503206018519", "[$-424]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 pop. tor."},
{"43543.503206018519", "[$-424]mmmmmm dd yyyy h:mm AM/PM dddd", "marec 19 2019 12:04 pop. torek"},
{"44562.189571759256", "[$-77]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 GH"},
{"44562.189571759256", "[$-77]mmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"},
{"44562.189571759256", "[$-77]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 GH"},
{"44562.189571759256", "[$-77]mmmmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"},
{"43543.503206018519", "[$-77]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 GD"},
{"43543.503206018519", "[$-77]mmmm dd yyyy h:mm AM/PM aaa", "Maarso 19 2019 12:04 GD Tldo"},
{"43543.503206018519", "[$-77]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 GD Tldo"},
{"43543.503206018519", "[$-77]mmmmmm dd yyyy h:mm AM/PM dddd", "Maarso 19 2019 12:04 GD Talaado"},
{"44562.189571759256", "[$-477]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 GH"},
{"44562.189571759256", "[$-477]mmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"},
{"44562.189571759256", "[$-477]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 GH"},
{"44562.189571759256", "[$-477]mmmmmm dd yyyy h:mm AM/PM", "Jannaayo 01 2022 4:32 GH"},
{"43543.503206018519", "[$-477]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 GD"},
{"43543.503206018519", "[$-477]mmmm dd yyyy h:mm AM/PM aaa", "Maarso 19 2019 12:04 GD Tldo"},
{"43543.503206018519", "[$-477]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 GD Tldo"},
{"43543.503206018519", "[$-477]mmmmmm dd yyyy h:mm AM/PM dddd", "Maarso 19 2019 12:04 GD Talaado"},
{"44562.189571759256", "[$-30]mmm dd yyyy h:mm AM/PM", "Phe 01 2022 4:32 AM"},
{"44562.189571759256", "[$-30]mmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"},
{"44562.189571759256", "[$-30]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"},
{"44562.189571759256", "[$-30]mmmmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"},
{"43543.503206018519", "[$-30]mmm dd yyyy h:mm AM/PM", "Ube 19 2019 12:04 PM"},
{"43543.503206018519", "[$-30]mmmm dd yyyy h:mm AM/PM aaa", "Hlakubele 19 2019 12:04 PM Bed"},
{"43543.503206018519", "[$-30]mmmmm dd yyyy h:mm AM/PM ddd", "H 19 2019 12:04 PM Bed"},
{"43543.503206018519", "[$-30]mmmmmm dd yyyy h:mm AM/PM dddd", "Hlakubele 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-430]mmm dd yyyy h:mm AM/PM", "Phe 01 2022 4:32 AM"},
{"44562.189571759256", "[$-430]mmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"},
{"44562.189571759256", "[$-430]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"},
{"44562.189571759256", "[$-430]mmmmmm dd yyyy h:mm AM/PM", "Phesekgong 01 2022 4:32 AM"},
{"43543.503206018519", "[$-430]mmm dd yyyy h:mm AM/PM", "Ube 19 2019 12:04 PM"},
{"43543.503206018519", "[$-430]mmmm dd yyyy h:mm AM/PM aaa", "Hlakubele 19 2019 12:04 PM Bed"},
{"43543.503206018519", "[$-430]mmmmm dd yyyy h:mm AM/PM ddd", "H 19 2019 12:04 PM Bed"},
{"43543.503206018519", "[$-430]mmmmmm dd yyyy h:mm AM/PM dddd", "Hlakubele 19 2019 12:04 PM Labobedi"},
{"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."},
{"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."},
{"44562.189571759256", "[$-A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. ma."},
{"43543.503206018519", "[$-A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. ma."},
{"43543.503206018519", "[$-A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-2C0A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-2C0A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-2C0A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-2C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-2C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-2C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-200A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-200A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-200A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-200A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-200A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-200A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-400A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-400A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-400A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-400A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-400A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-400A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-340A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-340A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-340A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-340A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-340A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-340A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-240A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-240A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-240A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-240A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-240A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-240A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-140A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-140A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-140A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-140A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-140A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-140A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-5C0A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-5C0A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a.m."},
{"44562.189571759256", "[$-5C0A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a.m."},
{"43543.503206018519", "[$-5C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-5C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-5C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p.m. martes"},
{"43543.503206018519", "[$-1C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-1C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-1C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-300A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-300A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-300A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-440A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-440A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-440A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-100A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-100A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-100A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-480A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-480A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-480A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-580A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-580A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p.m. mar."},
{"43543.503206018519", "[$-580A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p.m. martes"},
{"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."},
{"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM", "marzo 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "m 19 2019 12:04 p. m."},
{"44562.189571759256", "[$-80A]mmm dd yyyy h:mm AM/PM", "ene 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmm dd yyyy h:mm AM/PM", "enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-80A]mmmmm dd yyyy h:mm AM/PM", "e 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-80A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-80A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-80A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-4C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-4C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-4C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-180A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-180A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-180A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-3C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-3C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-3C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-280A]mmm dd yyyy h:mm AM/PM", "Ene. 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-280A]mmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-280A]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-280A]mmmmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-280A]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-280A]mmmm dd yyyy h:mm AM/PM aaa", "Marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-280A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-280A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzo 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-500A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-500A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-500A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 p. m. martes"},
{"43543.503206018519", "[$-40A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM ma."},
{"43543.503206018519", "[$-40A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM ma."},
{"43543.503206018519", "[$-40A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"},
{"43543.503206018519", "[$-C0A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM ma."},
{"43543.503206018519", "[$-C0A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM ma."},
{"43543.503206018519", "[$-C0A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"},
{"43543.503206018519", "[$-540A]mmm dd yyyy h:mm AM/PM aaa", "mar 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-540A]mmmm dd yyyy h:mm AM/PM ddd", "marzo 19 2019 12:04 PM mar"},
{"43543.503206018519", "[$-540A]mmmmm dd yyyy h:mm AM/PM dddd", "m 19 2019 12:04 PM martes"},
{"44562.189571759256", "[$-380A]mmm dd yyyy h:mm AM/PM", "Ene. 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-380A]mmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-380A]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-380A]mmmmmm dd yyyy h:mm AM/PM", "Enero 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-380A]mmm dd yyyy h:mm AM/PM", "Mar. 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-380A]mmmm dd yyyy h:mm AM/PM aaa", "Marzo 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-380A]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 p. m. mar."},
{"43543.503206018519", "[$-380A]mmmmmm dd yyyy h:mm AM/PM dddd", "Marzo 19 2019 12:04 p. m. martes"},
{"44562.189571759256", "[$-1D]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-1D]mmm dd yyyy h:mm AM/PM", "mar 19 2019 12:04 PM"},
{"43543.503206018519", "[$-1D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM tis"},
{"43543.503206018519", "[$-1D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tis"},
{"43543.503206018519", "[$-1D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM tisdag"},
{"44562.189571759256", "[$-81D]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 fm"},
{"44562.189571759256", "[$-81D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 fm"},
{"44562.189571759256", "[$-81D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 fm"},
{"44562.189571759256", "[$-81D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 fm"},
{"43543.503206018519", "[$-81D]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 em"},
{"43543.503206018519", "[$-81D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 em tis"},
{"43543.503206018519", "[$-81D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 em tis"},
{"43543.503206018519", "[$-81D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 em tisdag"},
{"44562.189571759256", "[$-41D]mmm dd yyyy h:mm AM/PM", "jan. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41D]mmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41D]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41D]mmmmmm dd yyyy h:mm AM/PM", "januari 01 2022 4:32 AM"},
{"43543.503206018519", "[$-41D]mmm dd yyyy h:mm AM/PM", "mars 19 2019 12:04 PM"},
{"43543.503206018519", "[$-41D]mmmm dd yyyy h:mm AM/PM aaa", "mars 19 2019 12:04 PM tis"},
{"43543.503206018519", "[$-41D]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 PM tis"},
{"43543.503206018519", "[$-41D]mmmmmm dd yyyy h:mm AM/PM dddd", "mars 19 2019 12:04 PM tisdag"},
{"44562.189571759256", "[$-5A]mmm dd yyyy h:mm AM/PM", "\u071F\u0722 \u070F\u0712 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-5A]mmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-5A]mmmmm dd yyyy h:mm AM/PM", "\u071F 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-5A]mmmmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"},
{"43543.503206018519", "[$-5A]mmm dd yyyy h:mm AM/PM", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B"},
{"43543.503206018519", "[$-5A]mmmm dd yyyy h:mm AM/PM aaa", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"},
{"43543.503206018519", "[$-5A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0710 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"},
{"43543.503206018519", "[$-5A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710"},
{"44562.189571759256", "[$-45A]mmm dd yyyy h:mm AM/PM", "\u071F\u0722 \u070F\u0712 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-45A]mmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-45A]mmmmm dd yyyy h:mm AM/PM", "\u071F 01 2022 4:32 \u0729.\u071B"},
{"44562.189571759256", "[$-45A]mmmmmm dd yyyy h:mm AM/PM", "\u071F\u0722\u0718\u0722 \u0710\u071A\u072A\u071D 01 2022 4:32 \u0729.\u071B"},
{"43543.503206018519", "[$-45A]mmm dd yyyy h:mm AM/PM", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B"},
{"43543.503206018519", "[$-45A]mmmm dd yyyy h:mm AM/PM aaa", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"},
{"43543.503206018519", "[$-45A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0710 19 2019 12:04 \u0712.\u071B \u070F\u0713 \u070F\u0712\u072B"},
{"43543.503206018519", "[$-45A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0710\u0715\u072A 19 2019 12:04 \u0712.\u071B \u072C\u0720\u072C\u0710 \u0712\u072B\u0712\u0710"},
{"44562.189571759256", "[$-28]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"},
{"44562.189571759256", "[$-28]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-28]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-28]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-28]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-28]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-28]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-28]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"},
{"44562.189571759256", "[$-7C28]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C28]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C28]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C28]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C28]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C28]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-7C28]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-7C28]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"},
{"44562.189571759256", "[$-428]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 AM"},
{"44562.189571759256", "[$-428]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-428]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-428]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-428]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-428]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-428]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0448\u0431"},
{"43543.503206018519", "[$-428]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0435\u0448\u0430\u043D\u0431\u0435"},
{"44562.189571759256", "[$-5F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-5F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"43543.503206018519", "[$-5F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"},
{"43543.503206018519", "[$-5F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-5F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-5F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"},
{"44562.189571759256", "[$-7C5F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-7C5F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"43543.503206018519", "[$-7C5F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"},
{"43543.503206018519", "[$-7C5F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-7C5F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-7C5F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"},
{"44562.189571759256", "[$-85F]mmm dd yyyy h:mm AM/PM", "Yen 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85F]mmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85F]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 AM"},
{"44562.189571759256", "[$-85F]mmmmmm dd yyyy h:mm AM/PM", "Yennayer 01 2022 4:32 AM"},
{"43543.503206018519", "[$-85F]mmm dd yyyy h:mm AM/PM", "Megh 19 2019 12:04 PM"},
{"43543.503206018519", "[$-85F]mmmm dd yyyy h:mm AM/PM aaa", "Meghres 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-85F]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM ttl"},
{"43543.503206018519", "[$-85F]mmmmmm dd yyyy h:mm AM/PM dddd", "Meghres 19 2019 12:04 PM ttlata"},
{"44562.189571759256", "[$-49]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-49]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-49]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-49]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-49]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-49]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"},
{"43543.503206018519", "[$-49]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"},
{"43543.503206018519", "[$-49]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8"},
{"44562.189571759256", "[$-449]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-449]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-449]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-449]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-449]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-449]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"},
{"43543.503206018519", "[$-449]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"},
{"43543.503206018519", "[$-449]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB4\u0BAE\u0BC8"},
{"44562.189571759256", "[$-849]mmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9. 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-849]mmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-849]mmmmm dd yyyy h:mm AM/PM", "\u0B9C 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"44562.189571759256", "[$-849]mmmmmm dd yyyy h:mm AM/PM", "\u0B9C\u0BA9\u0BB5\u0BB0\u0BBF 01 2022 4:32 \u0B95\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-849]mmm dd yyyy h:mm AM/PM", "\u0BAE\u0BBE\u0BB0\u0BCD. 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8"},
{"43543.503206018519", "[$-849]mmmm dd yyyy h:mm AM/PM aaa", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD."},
{"43543.503206018519", "[$-849]mmmmm dd yyyy h:mm AM/PM ddd", "\u0BAE 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD."},
{"43543.503206018519", "[$-849]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0BAE\u0BBE\u0BB0\u0BCD\u0B9A\u0BCD 19 2019 12:04 \u0BAE\u0BBE\u0BB2\u0BC8 \u0B9A\u0BC6\u0BB5\u0BCD\u0BB5\u0BBE\u0BAF\u0BCD"},
{"44562.189571759256", "[$-44]mmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44]mmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44]mmmmm dd yyyy h:mm AM/PM", "\u0433 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44]mmmmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-44]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-44]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448."},
{"43543.503206018519", "[$-44]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0438\u0448."},
{"43543.503206018519", "[$-44]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448\u04D9\u043C\u0431\u0435"},
{"44562.189571759256", "[$-444]mmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-444]mmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"44562.189571759256", "[$-444]mmmmm dd yyyy h:mm AM/PM", "\u0433 01 2022 4:32 AM"},
{"44562.189571759256", "[$-444]mmmmmm dd yyyy h:mm AM/PM", "\u0433\u044B\u0439\u043D\u0432\u0430\u0440 01 2022 4:32 AM"},
{"43543.503206018519", "[$-444]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440. 19 2019 12:04 PM"},
{"43543.503206018519", "[$-444]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448."},
{"43543.503206018519", "[$-444]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 PM \u0441\u0438\u0448."},
{"43543.503206018519", "[$-444]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 PM \u0441\u0438\u0448\u04D9\u043C\u0431\u0435"},
{"44562.189571759256", "[$-4A]mmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4A]mmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4A]mmmmm dd yyyy h:mm AM/PM", "\u0C1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-4A]mmmmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"},
{"43543.503206018519", "[$-4A]mmm dd yyyy h:mm AM/PM", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM"},
{"43543.503206018519", "[$-4A]mmmm dd yyyy h:mm AM/PM aaa", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"},
{"43543.503206018519", "[$-4A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0C2E 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"},
{"43543.503206018519", "[$-4A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02"},
{"44562.189571759256", "[$-44A]mmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44A]mmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44A]mmmmm dd yyyy h:mm AM/PM", "\u0C1C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-44A]mmmmmm dd yyyy h:mm AM/PM", "\u0C1C\u0C28\u0C35\u0C30\u0C3F 01 2022 4:32 AM"},
{"43543.503206018519", "[$-44A]mmm dd yyyy h:mm AM/PM", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM"},
{"43543.503206018519", "[$-44A]mmmm dd yyyy h:mm AM/PM aaa", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"},
{"43543.503206018519", "[$-44A]mmmmm dd yyyy h:mm AM/PM ddd", "\u0C2E 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33"},
{"43543.503206018519", "[$-44A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0C2E\u0C3E\u0C30\u0C4D\u0C1A\u0C3F 19 2019 12:04 PM \u0C2E\u0C02\u0C17\u0C33\u0C35\u0C3E\u0C30\u0C02"},
{"44562.189571759256", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E18. 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E40.\u0E22. 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E04. 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E22. 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E04. 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E2A.\u0E04. 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E22. 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E15.\u0E04. 01 2022 4:32 AM"},
{"44866.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E22. 01 2022 4:32 AM"},
{"44896.18957170139", "[$-1E]mmm dd yyyy h:mm AM/PM", "\u0E18.\u0E04. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44866.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44896.18957170139", "[$-1E]mmmm dd yyyy h:mm AM/PM", "\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44562.189571759256", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44593.189571759256", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44621.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44652.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E40 01 2022 4:32 AM"},
{"44682.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E1E 01 2022 4:32 AM"},
{"44713.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44743.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44774.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E2A 01 2022 4:32 AM"},
{"44805.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44835.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM aaa", "\u0E15 01 2022 4:32 AM \u0E2A."},
{"44866.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM ddd", "\u0E1E 01 2022 4:32 AM \u0E2D."},
{"44896.18957170139", "[$-1E]mmmmm dd yyyy h:mm AM/PM dddd", "\u0E18 01 2022 4:32 AM \u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35"},
{"44562.189571759256", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E18. 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E04. 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E40.\u0E22. 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E04. 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E21.\u0E22. 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E04. 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E2A.\u0E04. 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E01.\u0E22. 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E15.\u0E04. 01 2022 4:32 AM"},
{"44866.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E1E.\u0E22. 01 2022 4:32 AM"},
{"44896.18957170139", "[$-41E]mmm dd yyyy h:mm AM/PM", "\u0E18.\u0E04. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E01\u0E23\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E38\u0E21\u0E20\u0E32\u0E1E\u0E31\u0E19\u0E18\u0E4C 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E35\u0E19\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E40\u0E21\u0E29\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E29\u0E20\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E21\u0E34\u0E16\u0E38\u0E19\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E23\u0E01\u0E0E\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E2A\u0E34\u0E07\u0E2B\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E01\u0E31\u0E19\u0E22\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E15\u0E38\u0E25\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44866.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E1E\u0E24\u0E28\u0E08\u0E34\u0E01\u0E32\u0E22\u0E19 01 2022 4:32 AM"},
{"44896.18957170139", "[$-41E]mmmm dd yyyy h:mm AM/PM", "\u0E18\u0E31\u0E19\u0E27\u0E32\u0E04\u0E21 01 2022 4:32 AM"},
{"44562.189571759256", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44593.189571759256", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44621.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44652.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E40 01 2022 4:32 AM"},
{"44682.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E1E 01 2022 4:32 AM"},
{"44713.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E21 01 2022 4:32 AM"},
{"44743.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44774.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E2A 01 2022 4:32 AM"},
{"44805.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0E01 01 2022 4:32 AM"},
{"44835.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM aaa", "\u0E15 01 2022 4:32 AM \u0E2A."},
{"44866.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM ddd", "\u0E1E 01 2022 4:32 AM \u0E2D."},
{"44896.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM dddd", "\u0E18 01 2022 4:32 AM \u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35"},
{"100", "g\"年\"m\"月\"d\"日\";@", "年4月9日"},
{"100", "e\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
{"100", "ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
{"100", "[$-411]ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
{"43709", "[$-411]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"},
{"43709", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u4EE41年9月1日"},
{"43709", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C1年9月1日"},
{"43709", "[$-411]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"},
{"43709", "[$-411]ggee\"年\"m\"月\"d\"日\";@", "\u4EE401年9月1日"},
{"43709", "[$-411]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C01年9月1日"},
{"43709", "[$-ja-JP-x-gannen]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"},
{"43709", "[$-ja-JP-x-gannen]gge\"年\"m\"月\"d\"日\";@", "\u4EE41年9月1日"},
{"43709", "[$-ja-JP-x-gannen]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C1年9月1日"},
{"43709", "[$-ja-JP-x-gannen]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"},
{"43709", "[$-ja-JP-x-gannen]ggee\"年\"m\"月\"d\"日\";@", "\u4EE401年9月1日"},
{"43709", "[$-ja-JP-x-gannen]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C01年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]gge\"年\"m\"月\"d\"日\";@", "\u4EE4\u5143年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C\u5143年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]gee\"年\"m\"月\"d\"日\";@", "R01年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]ggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u5143年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]gggee\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C\u5143年9月1日"},
{"43466.189571759256", "[$-411]ge\"年\"m\"月\"d\"日\";@", "H31年1月1日"},
{"43466.189571759256", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u5E7331年1月1日"},
{"43466.189571759256", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u5E73\u621031年1月1日"},
{"44896.18957170139", "[$-411]ge\"年\"m\"月\"d\"日\";@", "R4年12月1日"},
{"44896.18957170139", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u4EE44年12月1日"},
{"44896.18957170139", "[$-411]ggge\"年\"m\"月\"d\"日\";@", "\u4EE4\u548C4年12月1日"},
{"44562.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44866.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44896.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44562.189571759256", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44866.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44896.18957170139", "[$-51]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44562.189571759256", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D"},
{"44866.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D"},
{"44896.18957170139", "[$-51]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D"},
{"44562.189571759256", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F23 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F24 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F25 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F26 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F27 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F28 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F29 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F20 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44866.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F21 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44896.18957170139", "[$-451]mmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B \u0F21\u0F22 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44562.189571759256", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F44\u0F0B\u0F54\u0F7C\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F42\u0F66\u0F74\u0F58\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F5E\u0F72\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F63\u0F94\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0FB2\u0F74\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F51\u0F74\u0F53\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F62\u0F92\u0FB1\u0F51\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F51\u0F42\u0F74\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F66\u0FA4\u0FB1\u0F72\u0F0B\u0F5F\u0FB3\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F54\u0F0D 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44866.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F45\u0F72\u0F42\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44896.18957170139", "[$-451]mmmm dd yyyy h:mm AM/PM", "\u0F5F\u0FB3\u0F0B\u0F56\u0F0B\u0F56\u0F45\u0F74\u0F0B\u0F42\u0F49\u0F72\u0F66\u0F0B\u0F54\u0F0B 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44562.189571759256", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44593.189571759256", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44621.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44652.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44682.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44713.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44743.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44774.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44805.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B"},
{"44835.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM aaa", "\u0F66 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F66\u0FA4\u0F7A\u0F53\u0F0B\u0F54\u0F0D"},
{"44866.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM ddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F58\u0F72\u0F42\u0F0B\u0F51\u0F58\u0F62\u0F0D"},
{"44896.18957170139", "[$-451]mmmmm dd yyyy h:mm AM/PM dddd", "\u0F5F 01 2022 4:32 \u0F66\u0F94\u0F0B\u0F51\u0FB2\u0F7C\u0F0B \u0F42\u0F5F\u0F60\u0F0B\u0F55\u0F74\u0F62\u0F0B\u0F56\u0F74\u0F0D"},
{"44562.189571759256", "[$-73]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-73]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-73]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-73]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"43543.503206018519", "[$-73]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A"},
{"43543.503206018519", "[$-73]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"},
{"43543.503206018519", "[$-73]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"},
{"43543.503206018519", "[$-73]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1220\u1209\u1235"},
{"44562.189571759256", "[$-873]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"},
{"44562.189571759256", "[$-873]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"},
{"44562.189571759256", "[$-873]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"},
{"44562.189571759256", "[$-873]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206 \u1230\u12D3\u1270"},
{"43543.503206018519", "[$-873]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275"},
{"43543.503206018519", "[$-873]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1230\u1209"},
{"43543.503206018519", "[$-873]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1230\u1209"},
{"43543.503206018519", "[$-873]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122D \u1230\u12D3\u1275 \u1220\u1209\u1235"},
{"44562.189571759256", "[$-473]mmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-473]mmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-473]mmmmm dd yyyy h:mm AM/PM", "\u1325 01 2022 4:32 \u1295\u1309\u1206"},
{"44562.189571759256", "[$-473]mmmmmm dd yyyy h:mm AM/PM", "\u1325\u122A 01 2022 4:32 \u1295\u1309\u1206"},
{"43543.503206018519", "[$-473]mmm dd yyyy h:mm AM/PM", "\u1218\u130B 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A"},
{"43543.503206018519", "[$-473]mmmm dd yyyy h:mm AM/PM aaa", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"},
{"43543.503206018519", "[$-473]mmmmm dd yyyy h:mm AM/PM ddd", "\u1218 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1230\u1209"},
{"43543.503206018519", "[$-473]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1218\u130B\u1262\u1275 19 2019 12:04 \u12F5\u1215\u122A \u1250\u1275\u122A \u1220\u1209\u1235"},
{"44562.189571759256", "[$-31]mmm dd yyyy h:mm AM/PM", "Sun 01 2022 4:32 AM"},
{"44562.189571759256", "[$-31]mmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"},
{"44562.189571759256", "[$-31]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"},
{"44562.189571759256", "[$-31]mmmmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"},
{"43543.503206018519", "[$-31]mmm dd yyyy h:mm AM/PM", "Kul 19 2019 12:04 PM"},
{"43543.503206018519", "[$-31]mmmm dd yyyy h:mm AM/PM aaa", "Nyenyankulu 19 2019 12:04 PM Bir"},
{"43543.503206018519", "[$-31]mmmmm dd yyyy h:mm AM/PM ddd", "N 19 2019 12:04 PM Bir"},
{"43543.503206018519", "[$-31]mmmmmm dd yyyy h:mm AM/PM dddd", "Nyenyankulu 19 2019 12:04 PM Ravumbirhi"},
{"44562.189571759256", "[$-431]mmm dd yyyy h:mm AM/PM", "Sun 01 2022 4:32 AM"},
{"44562.189571759256", "[$-431]mmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"},
{"44562.189571759256", "[$-431]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"},
{"44562.189571759256", "[$-431]mmmmmm dd yyyy h:mm AM/PM", "Sunguti 01 2022 4:32 AM"},
{"43543.503206018519", "[$-431]mmm dd yyyy h:mm AM/PM", "Kul 19 2019 12:04 PM"},
{"43543.503206018519", "[$-431]mmmm dd yyyy h:mm AM/PM aaa", "Nyenyankulu 19 2019 12:04 PM Bir"},
{"43543.503206018519", "[$-431]mmmmm dd yyyy h:mm AM/PM ddd", "N 19 2019 12:04 PM Bir"},
{"43543.503206018519", "[$-431]mmmmmm dd yyyy h:mm AM/PM dddd", "Nyenyankulu 19 2019 12:04 PM Ravumbirhi"},
{"44562.189571759256", "[$-1F]mmm dd yyyy h:mm AM/PM", "Oca 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-1F]mmm dd yyyy h:mm AM/PM", "Şub 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Mar 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Nis 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "May 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Haz 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Tem 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Ağu 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Eyl 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Eki 01 2022 4:32 \u00F6\u00F6"},
{"44866.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Kas 01 2022 4:32 \u00F6\u00F6"},
{"44896.18957170139", "[$-1F]mmm dd yyyy h:mm AM/PM", "Ara 01 2022 4:32 \u00F6\u00F6"},
{"44562.189571759256", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ocak 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Şubat 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Mart 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Nisan 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Mayıs 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Haziran 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Temmuz 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ağustos 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Eylül 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Ekim 01 2022 4:32 \u00F6\u00F6"},
{"44866.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Kasım 01 2022 4:32 \u00F6\u00F6"},
{"44896.18957170139", "[$-1F]mmmm dd yyyy h:mm AM/PM", "Aralık 01 2022 4:32 \u00F6\u00F6"},
{"44562.189571759256", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "Ş 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "H 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM aaa", "E 01 2022 4:32 \u00F6\u00F6 Cmt"},
{"44866.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM ddd", "K 01 2022 4:32 \u00F6\u00F6 Sal"},
{"44896.18957170139", "[$-1F]mmmmm dd yyyy h:mm AM/PM dddd", "A 01 2022 4:32 \u00F6\u00F6 Perşembe"},
{"44562.189571759256", "[$-41F]mmm dd yyyy h:mm AM/PM", "Oca 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-41F]mmm dd yyyy h:mm AM/PM", "Şub 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Mar 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Nis 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "May 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Haz 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Tem 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Ağu 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Eyl 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Eki 01 2022 4:32 \u00F6\u00F6"},
{"44866.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Kas 01 2022 4:32 \u00F6\u00F6"},
{"44896.18957170139", "[$-41F]mmm dd yyyy h:mm AM/PM", "Ara 01 2022 4:32 \u00F6\u00F6"},
{"44562.189571759256", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ocak 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Şubat 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Mart 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Nisan 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Mayıs 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Haziran 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Temmuz 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ağustos 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Eylül 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Ekim 01 2022 4:32 \u00F6\u00F6"},
{"44866.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Kasım 01 2022 4:32 \u00F6\u00F6"},
{"44896.18957170139", "[$-41F]mmmm dd yyyy h:mm AM/PM", "Aralık 01 2022 4:32 \u00F6\u00F6"},
{"44562.189571759256", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 \u00F6\u00F6"},
{"44593.189571759256", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "Ş 01 2022 4:32 \u00F6\u00F6"},
{"44621.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"},
{"44652.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 \u00F6\u00F6"},
{"44682.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 \u00F6\u00F6"},
{"44713.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "H 01 2022 4:32 \u00F6\u00F6"},
{"44743.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "T 01 2022 4:32 \u00F6\u00F6"},
{"44774.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 \u00F6\u00F6"},
{"44805.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 \u00F6\u00F6"},
{"44835.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM aaa", "E 01 2022 4:32 \u00F6\u00F6 Cmt"},
{"44866.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM ddd", "K 01 2022 4:32 \u00F6\u00F6 Sal"},
{"44896.18957170139", "[$-41F]mmmmm dd yyyy h:mm AM/PM dddd", "A 01 2022 4:32 \u00F6\u00F6 Perşembe"},
{"44562.189571759256", "[$-42]mmm dd yyyy h:mm AM/PM", "Ýan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42]mmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42]mmmmm dd yyyy h:mm AM/PM", "Ý 01 2022 4:32 AM"},
{"44562.189571759256", "[$-42]mmmmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-42]mmm dd yyyy h:mm AM/PM", "Mart 19 2019 12:04 PM"},
{"43543.503206018519", "[$-42]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 PM Sb"},
{"43543.503206018519", "[$-42]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sb"},
{"43543.503206018519", "[$-42]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 PM Sişenbe"},
{"44562.189571759256", "[$-442]mmm dd yyyy h:mm AM/PM", "Ýan 01 2022 4:32 AM"},
{"44562.189571759256", "[$-442]mmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"},
{"44562.189571759256", "[$-442]mmmmm dd yyyy h:mm AM/PM", "Ý 01 2022 4:32 AM"},
{"44562.189571759256", "[$-442]mmmmmm dd yyyy h:mm AM/PM", "Ýanwar 01 2022 4:32 AM"},
{"43543.503206018519", "[$-442]mmm dd yyyy h:mm AM/PM", "Mart 19 2019 12:04 PM"},
{"43543.503206018519", "[$-442]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 PM Sb"},
{"43543.503206018519", "[$-442]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 PM Sb"},
{"43543.503206018519", "[$-442]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 PM Sişenbe"},
{"44562.189571759256", "[$-22]mmm dd yyyy h:mm AM/PM", "\u0421\u0456\u0447 01 2022 4:32 AM"},
{"44562.189571759256", "[$-22]mmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-22]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"},
{"44562.189571759256", "[$-22]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"},
{"43543.503206018519", "[$-22]mmm dd yyyy h:mm AM/PM", "\u0411\u0435\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-22]mmmm dd yyyy h:mm AM/PM aaa", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-22]mmmmm dd yyyy h:mm AM/PM ddd", "\u0431 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-22]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A"},
{"44562.189571759256", "[$-422]mmm dd yyyy h:mm AM/PM", "\u0421\u0456\u0447 01 2022 4:32 AM"},
{"44562.189571759256", "[$-422]mmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-422]mmmmm dd yyyy h:mm AM/PM", "\u0441 01 2022 4:32 AM"},
{"44562.189571759256", "[$-422]mmmmmm dd yyyy h:mm AM/PM", "\u0441\u0456\u0447\u0435\u043D\u044C 01 2022 4:32 AM"},
{"43543.503206018519", "[$-422]mmm dd yyyy h:mm AM/PM", "\u0411\u0435\u0440 19 2019 12:04 PM"},
{"43543.503206018519", "[$-422]mmmm dd yyyy h:mm AM/PM aaa", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-422]mmmmm dd yyyy h:mm AM/PM ddd", "\u0431 19 2019 12:04 PM \u0412\u0442"},
{"43543.503206018519", "[$-422]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0431\u0435\u0440\u0435\u0437\u0435\u043D\u044C 19 2019 12:04 PM \u0432\u0456\u0432\u0442\u043E\u0440\u043E\u043A"},
{"44562.189571759256", "[$-2E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-2E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-2E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-2E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"},
{"43543.503206018519", "[$-2E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 popołdnju"},
{"43543.503206018519", "[$-2E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 popołdnju wut"},
{"43543.503206018519", "[$-2E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 popołdnju wut"},
{"43543.503206018519", "[$-2E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 popołdnju wutora"},
{"44562.189571759256", "[$-42E]mmm dd yyyy h:mm AM/PM", "jan 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-42E]mmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-42E]mmmmm dd yyyy h:mm AM/PM", "j 01 2022 4:32 dopołdnja"},
{"44562.189571759256", "[$-42E]mmmmmm dd yyyy h:mm AM/PM", "januar 01 2022 4:32 dopołdnja"},
{"43543.503206018519", "[$-42E]mmm dd yyyy h:mm AM/PM", "měr 19 2019 12:04 popołdnju"},
{"43543.503206018519", "[$-42E]mmmm dd yyyy h:mm AM/PM aaa", "měrc 19 2019 12:04 popołdnju wut"},
{"43543.503206018519", "[$-42E]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 popołdnju wut"},
{"43543.503206018519", "[$-42E]mmmmmm dd yyyy h:mm AM/PM dddd", "měrc 19 2019 12:04 popołdnju wutora"},
{"44562.189571759256", "[$-20]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-20]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-20]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-20]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-20]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-20]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-20]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-20]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"44562.189571759256", "[$-820]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"},
{"44562.189571759256", "[$-820]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"},
{"44562.189571759256", "[$-820]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 \u062F\u0646"},
{"44562.189571759256", "[$-820]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 \u062F\u0646"},
{"43543.503206018519", "[$-820]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A"},
{"43543.503206018519", "[$-820]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-820]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-820]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 \u0631\u0627\u062A \u0645\u0646\u06AF\u0644"},
{"44562.189571759256", "[$-420]mmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-420]mmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"44562.189571759256", "[$-420]mmmmm dd yyyy h:mm AM/PM", "\u062C 01 2022 4:32 AM"},
{"44562.189571759256", "[$-420]mmmmmm dd yyyy h:mm AM/PM", "\u062C\u0646\u0648\u0631\u06CC 01 2022 4:32 AM"},
{"43543.503206018519", "[$-420]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM"},
{"43543.503206018519", "[$-420]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-420]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"43543.503206018519", "[$-420]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0686 19 2019 12:04 PM \u0645\u0646\u06AF\u0644"},
{"44562.189571759256", "[$-80]mmm dd yyyy h:mm AM/PM", "1-\u0626\u0627\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-80]mmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-80]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-80]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"43543.503206018519", "[$-80]mmm dd yyyy h:mm AM/PM", "3-\u0626\u0627\u064A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646"},
{"43543.503206018519", "[$-80]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"},
{"43543.503206018519", "[$-80]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"},
{"43543.503206018519", "[$-80]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5"},
{"44562.189571759256", "[$-480]mmm dd yyyy h:mm AM/PM", "1-\u0626\u0627\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-480]mmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-480]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"44562.189571759256", "[$-480]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0627\u0646\u06CB\u0627\u0631 01 2022 4:32 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0628\u06C7\u0631\u06C7\u0646"},
{"43543.503206018519", "[$-480]mmm dd yyyy h:mm AM/PM", "3-\u0626\u0627\u064A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646"},
{"43543.503206018519", "[$-480]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"},
{"43543.503206018519", "[$-480]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5"},
{"43543.503206018519", "[$-480]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u062A 19 2019 12:04 \u0686\u06C8\u0634\u062A\u0649\u0646 \u0643\u06D0\u064A\u0649\u0646 \u0633\u06D5\u064A\u0634\u06D5\u0646\u0628\u06D5"},
{"44562.189571759256", "[$-7843]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-7843]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-7843]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-7843]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"},
{"43543.503206018519", "[$-7843]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 \u0422\u041A"},
{"43543.503206018519", "[$-7843]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"},
{"43543.503206018519", "[$-7843]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"},
{"43543.503206018519", "[$-7843]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448\u0430\u043D\u0431\u0430"},
{"44562.189571759256", "[$-843]mmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-843]mmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-843]mmmmm dd yyyy h:mm AM/PM", "\u044F 01 2022 4:32 \u0422\u041E"},
{"44562.189571759256", "[$-843]mmmmmm dd yyyy h:mm AM/PM", "\u044F\u043D\u0432\u0430\u0440 01 2022 4:32 \u0422\u041E"},
{"43543.503206018519", "[$-843]mmm dd yyyy h:mm AM/PM", "\u043C\u0430\u0440 19 2019 12:04 \u0422\u041A"},
{"43543.503206018519", "[$-843]mmmm dd yyyy h:mm AM/PM aaa", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"},
{"43543.503206018519", "[$-843]mmmmm dd yyyy h:mm AM/PM ddd", "\u043C 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448"},
{"43543.503206018519", "[$-843]mmmmmm dd yyyy h:mm AM/PM dddd", "\u043C\u0430\u0440\u0442 19 2019 12:04 \u0422\u041A \u0441\u0435\u0448\u0430\u043D\u0431\u0430"},
{"44562.189571759256", "[$-43]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"},
{"44562.189571759256", "[$-43]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"44562.189571759256", "[$-43]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"},
{"44562.189571759256", "[$-43]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"43543.503206018519", "[$-43]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"},
{"43543.503206018519", "[$-43]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-43]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-43]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"},
{"44562.189571759256", "[$-7C43]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"},
{"44562.189571759256", "[$-7C43]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"44562.189571759256", "[$-7C43]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"},
{"44562.189571759256", "[$-7C43]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"43543.503206018519", "[$-7C43]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"},
{"43543.503206018519", "[$-7C43]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-7C43]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-7C43]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"},
{"44562.189571759256", "[$-443]mmm dd yyyy h:mm AM/PM", "Yan 01 2022 4:32 TO"},
{"44562.189571759256", "[$-443]mmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"44562.189571759256", "[$-443]mmmmm dd yyyy h:mm AM/PM", "Y 01 2022 4:32 TO"},
{"44562.189571759256", "[$-443]mmmmmm dd yyyy h:mm AM/PM", "Yanvar 01 2022 4:32 TO"},
{"43543.503206018519", "[$-443]mmm dd yyyy h:mm AM/PM", "Mar 19 2019 12:04 TK"},
{"43543.503206018519", "[$-443]mmmm dd yyyy h:mm AM/PM aaa", "Mart 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-443]mmmmm dd yyyy h:mm AM/PM ddd", "M 19 2019 12:04 TK Sesh"},
{"43543.503206018519", "[$-443]mmmmmm dd yyyy h:mm AM/PM dddd", "Mart 19 2019 12:04 TK seshanba"},
{"44562.189571759256", "[$-803]mmm dd yyyy h:mm AM/PM", "gen. 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-803]mmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-803]mmmmm dd yyyy h:mm AM/PM", "g 01 2022 4:32 a. m."},
{"44562.189571759256", "[$-803]mmmmmm dd yyyy h:mm AM/PM", "gener 01 2022 4:32 a. m."},
{"43543.503206018519", "[$-803]mmm dd yyyy h:mm AM/PM", "març 19 2019 12:04 p. m."},
{"43543.503206018519", "[$-803]mmmm dd yyyy h:mm AM/PM aaa", "març 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-803]mmmmm dd yyyy h:mm AM/PM ddd", "m 19 2019 12:04 p. m. dt."},
{"43543.503206018519", "[$-803]mmmmmm dd yyyy h:mm AM/PM dddd", "març 19 2019 12:04 p. m. dimarts"},
{"44562.189571759256", "[$-33]mmm dd yyyy h:mm AM/PM", "Pha 01 2022 4:32 AM"},
{"44562.189571759256", "[$-33]mmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"},
{"44562.189571759256", "[$-33]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"},
{"44562.189571759256", "[$-33]mmmmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"},
{"43543.503206018519", "[$-33]mmm dd yyyy h:mm AM/PM", "Ṱhf 19 2019 12:04 PM"},
{"43543.503206018519", "[$-33]mmmm dd yyyy h:mm AM/PM aaa", "Ṱhafamuhwe 19 2019 12:04 PM Vhi"},
{"43543.503206018519", "[$-33]mmmmm dd yyyy h:mm AM/PM ddd", "Ṱ 19 2019 12:04 PM Vhi"},
{"43543.503206018519", "[$-33]mmmmmm dd yyyy h:mm AM/PM dddd", "Ṱhafamuhwe 19 2019 12:04 PM Ḽavhuvhili"},
{"44562.189571759256", "[$-433]mmm dd yyyy h:mm AM/PM", "Pha 01 2022 4:32 AM"},
{"44562.189571759256", "[$-433]mmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"},
{"44562.189571759256", "[$-433]mmmmm dd yyyy h:mm AM/PM", "P 01 2022 4:32 AM"},
{"44562.189571759256", "[$-433]mmmmmm dd yyyy h:mm AM/PM", "Phando 01 2022 4:32 AM"},
{"43543.503206018519", "[$-433]mmm dd yyyy h:mm AM/PM", "Ṱhf 19 2019 12:04 PM"},
{"43543.503206018519", "[$-433]mmmm dd yyyy h:mm AM/PM aaa", "Ṱhafamuhwe 19 2019 12:04 PM Vhi"},
{"43543.503206018519", "[$-433]mmmmm dd yyyy h:mm AM/PM ddd", "Ṱ 19 2019 12:04 PM Vhi"},
{"43543.503206018519", "[$-433]mmmmmm dd yyyy h:mm AM/PM dddd", "Ṱhafamuhwe 19 2019 12:04 PM Ḽavhuvhili"},
{"44562.189571759256", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 10 01 2022 4:32 SA"},
{"44866.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 11 01 2022 4:32 SA"},
{"44896.18957170139", "[$-2A]mmm dd yyyy h:mm AM/PM", "Thg 12 01 2022 4:32 SA"},
{"44562.189571759256", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 10 01 2022 4:32 SA"},
{"44866.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 11 01 2022 4:32 SA"},
{"44896.18957170139", "[$-2A]mmmm dd yyyy h:mm AM/PM", "Tháng 12 01 2022 4:32 SA"},
{"44562.189571759256", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM", "T 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM aaa", "T 10 01 2022 4:32 SA T7"},
{"44866.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM ddd", "T 11 01 2022 4:32 SA T3"},
{"44896.18957170139", "[$-2A]mmmmm dd yyyy h:mm AM/PM dddd", "T 12 01 2022 4:32 SA Th\u01B0\u0301 N\u0103m"},
{"44562.189571759256", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 10 01 2022 4:32 SA"},
{"44866.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 11 01 2022 4:32 SA"},
{"44896.18957170139", "[$-42A]mmm dd yyyy h:mm AM/PM", "Thg 12 01 2022 4:32 SA"},
{"44562.189571759256", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 10 01 2022 4:32 SA"},
{"44866.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 11 01 2022 4:32 SA"},
{"44896.18957170139", "[$-42A]mmmm dd yyyy h:mm AM/PM", "Tháng 12 01 2022 4:32 SA"},
{"44562.189571759256", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 1 01 2022 4:32 SA"},
{"44593.189571759256", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 2 01 2022 4:32 SA"},
{"44621.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 3 01 2022 4:32 SA"},
{"44652.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 4 01 2022 4:32 SA"},
{"44682.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 5 01 2022 4:32 SA"},
{"44713.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 6 01 2022 4:32 SA"},
{"44743.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 7 01 2022 4:32 SA"},
{"44774.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 8 01 2022 4:32 SA"},
{"44805.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM", "T 9 01 2022 4:32 SA"},
{"44835.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM aaa", "T 10 01 2022 4:32 SA T7"},
{"44866.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM ddd", "T 11 01 2022 4:32 SA T3"},
{"44896.18957170139", "[$-42A]mmmmm dd yyyy h:mm AM/PM dddd", "T 12 01 2022 4:32 SA Th\u01B0\u0301 N\u0103m"},
{"44562.189571759256", "[$-52]mmm dd yyyy h:mm AM/PM", "Ion 01 2022 4:32 yb"},
{"44593.189571759256", "[$-52]mmm dd yyyy h:mm AM/PM", "Chwef 01 2022 4:32 yb"},
{"44621.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Maw 01 2022 4:32 yb"},
{"44652.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Ebr 01 2022 4:32 yb"},
{"44682.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"},
{"44713.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Meh 01 2022 4:32 yb"},
{"44743.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Gorff 01 2022 4:32 yb"},
{"44774.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"},
{"44805.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"},
{"44835.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Hyd 01 2022 4:32 yb"},
{"44866.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Tach 01 2022 4:32 yb"},
{"44896.18957170139", "[$-52]mmm dd yyyy h:mm AM/PM", "Rhag 01 2022 4:32 yb"},
{"44562.189571759256", "[$-52]mmmm dd yyyy h:mm AM/PM", "Ionawr 01 2022 4:32 yb"},
{"44593.189571759256", "[$-52]mmmm dd yyyy h:mm AM/PM", "Chwefror 01 2022 4:32 yb"},
{"44621.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mawrth 01 2022 4:32 yb"},
{"44652.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Ebrill 01 2022 4:32 yb"},
{"44682.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"},
{"44713.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Mehefin 01 2022 4:32 yb"},
{"44743.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Gorffennaf 01 2022 4:32 yb"},
{"44774.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"},
{"44805.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"},
{"44835.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Hydref 01 2022 4:32 yb"},
{"44866.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Tachwedd 01 2022 4:32 yb"},
{"44896.18957170139", "[$-52]mmmm dd yyyy h:mm AM/PM", "Rhagfyr 01 2022 4:32 yb"},
{"44562.189571759256", "[$-52]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 yb"},
{"44593.189571759256", "[$-52]mmmmm dd yyyy h:mm AM/PM", "C 01 2022 4:32 yb"},
{"44621.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44652.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 yb"},
{"44682.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44713.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44743.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 yb"},
{"44774.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 yb"},
{"44805.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44835.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM aaa", "H 01 2022 4:32 yb Sad"},
{"44866.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM ddd", "T 01 2022 4:32 yb Maw"},
{"44896.18957170139", "[$-52]mmmmm dd yyyy h:mm AM/PM dddd", "R 01 2022 4:32 yb Dydd Iau"},
{"44562.189571759256", "[$-452]mmm dd yyyy h:mm AM/PM", "Ion 01 2022 4:32 yb"},
{"44593.189571759256", "[$-452]mmm dd yyyy h:mm AM/PM", "Chwef 01 2022 4:32 yb"},
{"44621.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Maw 01 2022 4:32 yb"},
{"44652.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Ebr 01 2022 4:32 yb"},
{"44682.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"},
{"44713.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Meh 01 2022 4:32 yb"},
{"44743.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Gorff 01 2022 4:32 yb"},
{"44774.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"},
{"44805.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"},
{"44835.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Hyd 01 2022 4:32 yb"},
{"44866.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Tach 01 2022 4:32 yb"},
{"44896.18957170139", "[$-452]mmm dd yyyy h:mm AM/PM", "Rhag 01 2022 4:32 yb"},
{"44562.189571759256", "[$-452]mmmm dd yyyy h:mm AM/PM", "Ionawr 01 2022 4:32 yb"},
{"44593.189571759256", "[$-452]mmmm dd yyyy h:mm AM/PM", "Chwefror 01 2022 4:32 yb"},
{"44621.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mawrth 01 2022 4:32 yb"},
{"44652.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Ebrill 01 2022 4:32 yb"},
{"44682.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mai 01 2022 4:32 yb"},
{"44713.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Mehefin 01 2022 4:32 yb"},
{"44743.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Gorffennaf 01 2022 4:32 yb"},
{"44774.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Awst 01 2022 4:32 yb"},
{"44805.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Medi 01 2022 4:32 yb"},
{"44835.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Hydref 01 2022 4:32 yb"},
{"44866.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Tachwedd 01 2022 4:32 yb"},
{"44896.18957170139", "[$-452]mmmm dd yyyy h:mm AM/PM", "Rhagfyr 01 2022 4:32 yb"},
{"44562.189571759256", "[$-452]mmmmm dd yyyy h:mm AM/PM", "I 01 2022 4:32 yb"},
{"44593.189571759256", "[$-452]mmmmm dd yyyy h:mm AM/PM", "C 01 2022 4:32 yb"},
{"44621.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44652.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 yb"},
{"44682.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44713.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44743.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "G 01 2022 4:32 yb"},
{"44774.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 yb"},
{"44805.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 yb"},
{"44835.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM aaa", "H 01 2022 4:32 yb Sad"},
{"44866.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM ddd", "T 01 2022 4:32 yb Maw"},
{"44896.18957170139", "[$-452]mmmmm dd yyyy h:mm AM/PM dddd", "R 01 2022 4:32 yb Dydd Iau"},
{"44562.189571759256", "[$-88]mmm dd yyyy h:mm AM/PM", "Sam. 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-88]mmm dd yyyy h:mm AM/PM", "Few. 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Maa 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Awr. 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Suw 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Sul. 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Sept. 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Okt. 01 2022 4:32 Sub"},
{"44866.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Now. 01 2022 4:32 Sub"},
{"44896.18957170139", "[$-88]mmm dd yyyy h:mm AM/PM", "Des. 01 2022 4:32 Sub"},
{"44562.189571759256", "[$-88]mmmm dd yyyy h:mm AM/PM", "Samwiye 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-88]mmmm dd yyyy h:mm AM/PM", "Fewriye 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Maars 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Awril 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Suwe 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Sullet 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Septàmbar 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Oktoobar 01 2022 4:32 Sub"},
{"44866.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Noowàmbar 01 2022 4:32 Sub"},
{"44896.18957170139", "[$-88]mmmm dd yyyy h:mm AM/PM", "Desàmbar 01 2022 4:32 Sub"},
{"44562.189571759256", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-88]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "U 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 Sub Gaa."},
{"44866.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 Sub Tal."},
{"44896.18957170139", "[$-88]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 Sub Alxames"},
{"44562.189571759256", "[$-488]mmm dd yyyy h:mm AM/PM", "Sam. 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-488]mmm dd yyyy h:mm AM/PM", "Few. 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Maa 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Awr. 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Suw 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Sul. 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Sept. 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Okt. 01 2022 4:32 Sub"},
{"44866.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Now. 01 2022 4:32 Sub"},
{"44896.18957170139", "[$-488]mmm dd yyyy h:mm AM/PM", "Des. 01 2022 4:32 Sub"},
{"44562.189571759256", "[$-488]mmmm dd yyyy h:mm AM/PM", "Samwiye 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-488]mmmm dd yyyy h:mm AM/PM", "Fewriye 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Maars 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Awril 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Me 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Suwe 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Sullet 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Ut 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Septàmbar 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Oktoobar 01 2022 4:32 Sub"},
{"44866.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Noowàmbar 01 2022 4:32 Sub"},
{"44896.18957170139", "[$-488]mmmm dd yyyy h:mm AM/PM", "Desàmbar 01 2022 4:32 Sub"},
{"44562.189571759256", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44593.189571759256", "[$-488]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 Sub"},
{"44621.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"},
{"44652.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 Sub"},
{"44682.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 Sub"},
{"44713.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44743.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44774.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "U 01 2022 4:32 Sub"},
{"44805.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 Sub"},
{"44835.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 Sub Gaa."},
{"44866.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 Sub Tal."},
{"44896.18957170139", "[$-488]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 Sub Alxames"},
{"44562.189571759256", "[$-34]mmm dd yyyy h:mm AM/PM", "uJan. 01 2022 4:32 AM"},
{"44593.189571759256", "[$-34]mmm dd yyyy h:mm AM/PM", "uFeb. 01 2022 4:32 AM"},
{"44621.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uMat. 01 2022 4:32 AM"},
{"44652.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uEpr. 01 2022 4:32 AM"},
{"44682.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uMey. 01 2022 4:32 AM"},
{"44713.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uJun. 01 2022 4:32 AM"},
{"44743.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uJul. 01 2022 4:32 AM"},
{"44774.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uAg. 01 2022 4:32 AM"},
{"44805.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uSep. 01 2022 4:32 AM"},
{"44835.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uOkt. 01 2022 4:32 AM"},
{"44866.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uNov. 01 2022 4:32 AM"},
{"44896.18957170139", "[$-34]mmm dd yyyy h:mm AM/PM", "uDis. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJanuwari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-34]mmmm dd yyyy h:mm AM/PM", "uFebuwari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uMatshi 01 2022 4:32 AM"},
{"44652.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uAprili 01 2022 4:32 AM"},
{"44682.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uMeyi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJuni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uJulayi 01 2022 4:32 AM"},
{"44774.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uAgasti 01 2022 4:32 AM"},
{"44805.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uSeptemba 01 2022 4:32 AM"},
{"44835.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uOktobha 01 2022 4:32 AM"},
{"44866.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uNovemba 01 2022 4:32 AM"},
{"44896.18957170139", "[$-34]mmmm dd yyyy h:mm AM/PM", "uDisemba 01 2022 4:32 AM"},
{"44562.189571759256", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44593.189571759256", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44621.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44652.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44682.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44713.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44743.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44774.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44805.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44835.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM aaa", "u 01 2022 4:32 AM uMgq."},
{"44866.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM ddd", "u 01 2022 4:32 AM uLwesib."},
{"44896.18957170139", "[$-34]mmmmm dd yyyy h:mm AM/PM dddd", "u 01 2022 4:32 AM Lwesine"},
{"44562.189571759256", "[$-434]mmm dd yyyy h:mm AM/PM", "uJan. 01 2022 4:32 AM"},
{"44593.189571759256", "[$-434]mmm dd yyyy h:mm AM/PM", "uFeb. 01 2022 4:32 AM"},
{"44621.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uMat. 01 2022 4:32 AM"},
{"44652.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uEpr. 01 2022 4:32 AM"},
{"44682.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uMey. 01 2022 4:32 AM"},
{"44713.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uJun. 01 2022 4:32 AM"},
{"44743.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uJul. 01 2022 4:32 AM"},
{"44774.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uAg. 01 2022 4:32 AM"},
{"44805.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uSep. 01 2022 4:32 AM"},
{"44835.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uOkt. 01 2022 4:32 AM"},
{"44866.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uNov. 01 2022 4:32 AM"},
{"44896.18957170139", "[$-434]mmm dd yyyy h:mm AM/PM", "uDis. 01 2022 4:32 AM"},
{"44562.189571759256", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJanuwari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-434]mmmm dd yyyy h:mm AM/PM", "uFebuwari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uMatshi 01 2022 4:32 AM"},
{"44652.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uAprili 01 2022 4:32 AM"},
{"44682.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uMeyi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJuni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uJulayi 01 2022 4:32 AM"},
{"44774.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uAgasti 01 2022 4:32 AM"},
{"44805.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uSeptemba 01 2022 4:32 AM"},
{"44835.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uOktobha 01 2022 4:32 AM"},
{"44866.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uNovemba 01 2022 4:32 AM"},
{"44896.18957170139", "[$-434]mmmm dd yyyy h:mm AM/PM", "uDisemba 01 2022 4:32 AM"},
{"44562.189571759256", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44593.189571759256", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44621.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44652.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44682.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44713.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44743.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44774.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44805.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM", "u 01 2022 4:32 AM"},
{"44835.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM aaa", "u 01 2022 4:32 AM uMgq."},
{"44866.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM ddd", "u 01 2022 4:32 AM uLwesib."},
{"44896.18957170139", "[$-434]mmmmm dd yyyy h:mm AM/PM dddd", "u 01 2022 4:32 AM Lwesine"},
{"44562.189571759256", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44866.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44896.18957170139", "[$-78]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44562.189571759256", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44866.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44896.18957170139", "[$-78]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44562.189571759256", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA2CD 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA44D 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA315 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA1D6 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA26C 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA0D8 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA3C3 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA246 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM", "\uA22C 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM aaa", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA0D8"},
{"44866.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM ddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA44D"},
{"44896.18957170139", "[$-78]mmmmm dd yyyy h:mm AM/PM dddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA282\uA1D6"},
{"44562.189571759256", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44866.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44896.18957170139", "[$-478]mmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44562.189571759256", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2CD\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA44D\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA315\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA1D6\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA26C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA0D8\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA3C3\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA246\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA22C\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44866.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA2AA\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44896.18957170139", "[$-478]mmmm dd yyyy h:mm AM/PM", "\uA2B0\uA44B\uA1AA 01 2022 4:32 \uA3B8\uA111"},
{"44562.189571759256", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA2CD 01 2022 4:32 \uA3B8\uA111"},
{"44593.189571759256", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA44D 01 2022 4:32 \uA3B8\uA111"},
{"44621.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA315 01 2022 4:32 \uA3B8\uA111"},
{"44652.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA1D6 01 2022 4:32 \uA3B8\uA111"},
{"44682.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA26C 01 2022 4:32 \uA3B8\uA111"},
{"44713.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA0D8 01 2022 4:32 \uA3B8\uA111"},
{"44743.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA3C3 01 2022 4:32 \uA3B8\uA111"},
{"44774.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA246 01 2022 4:32 \uA3B8\uA111"},
{"44805.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM", "\uA22C 01 2022 4:32 \uA3B8\uA111"},
{"44835.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM aaa", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA0D8"},
{"44866.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM ddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA44D"},
{"44896.18957170139", "[$-478]mmmmm dd yyyy h:mm AM/PM dddd", "\uA2B0 01 2022 4:32 \uA3B8\uA111 \uA18F\uA282\uA1D6"},
{"44562.189571759256", "[$-43D]mmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"},
{"44562.189571759256", "[$-43D]mmmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"},
{"44562.189571759256", "[$-43D]mmmmm dd yyyy h:mm AM/PM", "\u05D9 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"},
{"44562.189571759256", "[$-43D]mmmmmm dd yyyy h:mm AM/PM", "\u05D9\u05D0\u05B7\u05E0\u05D5\u05D0\u05B7\u05E8 01 2022 4:32 \u05E4\u05BF\u05D0\u05B7\u05E8\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"},
{"43543.503206018519", "[$-43D]mmm dd yyyy h:mm AM/PM", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2"},
{"43543.503206018519", "[$-43D]mmmm dd yyyy h:mm AM/PM aaa", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-43D]mmmmm dd yyyy h:mm AM/PM ddd", "\u05DE 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D9\u05D5\u05DD \u05D2"},
{"43543.503206018519", "[$-43D]mmmmmm dd yyyy h:mm AM/PM dddd", "\u05DE\u05E2\u05E8\u05E5 19 2019 12:04 \u05E0\u05D0\u05B8\u05DB\u05DE\u05D9\u05D8\u05D0\u05B8\u05D2 \u05D3\u05D9\u05E0\u05E1\u05D8\u05D9\u05E7"},
{"44562.189571759256", "[$-6A]mmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-6A]mmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-6A]mmmmm dd yyyy h:mm AM/PM", "\u1E62 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-6A]mmmmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"43543.503206018519", "[$-6A]mmm dd yyyy h:mm AM/PM", "\u1EB8r 19 2019 12:04 \u1ECC\u0300sán"},
{"43543.503206018519", "[$-6A]mmmm dd yyyy h:mm AM/PM aaa", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"},
{"43543.503206018519", "[$-6A]mmmmm dd yyyy h:mm AM/PM ddd", "\u1EB8 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"},
{"43543.503206018519", "[$-6A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán \u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun"},
{"44562.189571759256", "[$-46A]mmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-46A]mmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-46A]mmmmm dd yyyy h:mm AM/PM", "\u1E62 01 2022 4:32 Àár\u1ECD\u0300"},
{"44562.189571759256", "[$-46A]mmmmmm dd yyyy h:mm AM/PM", "\u1E62\u1EB9\u0301r\u1EB9\u0301 01 2022 4:32 Àár\u1ECD\u0300"},
{"43543.503206018519", "[$-46A]mmm dd yyyy h:mm AM/PM", "\u1EB8r 19 2019 12:04 \u1ECC\u0300sán"},
{"43543.503206018519", "[$-46A]mmmm dd yyyy h:mm AM/PM aaa", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"},
{"43543.503206018519", "[$-46A]mmmmm dd yyyy h:mm AM/PM ddd", "\u1EB8 19 2019 12:04 \u1ECC\u0300sán Ì\u1E63g"},
{"43543.503206018519", "[$-46A]mmmmmm dd yyyy h:mm AM/PM dddd", "\u1EB8r\u1EB9\u0300nà 19 2019 12:04 \u1ECC\u0300sán \u1ECCj\u1ECD\u0301 Ìs\u1EB9\u0301gun"},
{"44562.189571759256", "[$-35]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-35]mmm dd yyyy h:mm AM/PM", "Feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Mas 01 2022 4:32 AM"},
{"44652.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Eph 01 2022 4:32 AM"},
{"44682.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Mey 01 2022 4:32 AM"},
{"44713.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Agas 01 2022 4:32 AM"},
{"44805.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-35]mmm dd yyyy h:mm AM/PM", "Dis 01 2022 4:32 AM"},
{"44562.189571759256", "[$-35]mmmm dd yyyy h:mm AM/PM", "Januwari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-35]mmmm dd yyyy h:mm AM/PM", "Febhuwari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Mashi 01 2022 4:32 AM"},
{"44652.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Ephreli 01 2022 4:32 AM"},
{"44682.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Meyi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Julayi 01 2022 4:32 AM"},
{"44774.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Agasti 01 2022 4:32 AM"},
{"44805.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Septemba 01 2022 4:32 AM"},
{"44835.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Okthoba 01 2022 4:32 AM"},
{"44866.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Novemba 01 2022 4:32 AM"},
{"44896.18957170139", "[$-35]mmmm dd yyyy h:mm AM/PM", "Disemba 01 2022 4:32 AM"},
{"44562.189571759256", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44593.189571759256", "[$-35]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"},
{"44621.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44652.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 AM"},
{"44682.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44713.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44743.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44774.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 AM"},
{"44805.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"},
{"44835.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 AM Mgq."},
{"44866.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 AM Bi."},
{"44896.18957170139", "[$-35]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 AM ULwesine"},
{"44562.189571759256", "[$-435]mmm dd yyyy h:mm AM/PM", "Jan 01 2022 4:32 AM"},
{"44593.189571759256", "[$-435]mmm dd yyyy h:mm AM/PM", "Feb 01 2022 4:32 AM"},
{"44621.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Mas 01 2022 4:32 AM"},
{"44652.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Eph 01 2022 4:32 AM"},
{"44682.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Mey 01 2022 4:32 AM"},
{"44713.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Jun 01 2022 4:32 AM"},
{"44743.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Jul 01 2022 4:32 AM"},
{"44774.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Agas 01 2022 4:32 AM"},
{"44805.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Sep 01 2022 4:32 AM"},
{"44835.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Okt 01 2022 4:32 AM"},
{"44866.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Nov 01 2022 4:32 AM"},
{"44896.18957170139", "[$-435]mmm dd yyyy h:mm AM/PM", "Dis 01 2022 4:32 AM"},
{"44562.189571759256", "[$-435]mmmm dd yyyy h:mm AM/PM", "Januwari 01 2022 4:32 AM"},
{"44593.189571759256", "[$-435]mmmm dd yyyy h:mm AM/PM", "Febhuwari 01 2022 4:32 AM"},
{"44621.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Mashi 01 2022 4:32 AM"},
{"44652.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Ephreli 01 2022 4:32 AM"},
{"44682.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Meyi 01 2022 4:32 AM"},
{"44713.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Juni 01 2022 4:32 AM"},
{"44743.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Julayi 01 2022 4:32 AM"},
{"44774.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Agasti 01 2022 4:32 AM"},
{"44805.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Septemba 01 2022 4:32 AM"},
{"44835.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Okthoba 01 2022 4:32 AM"},
{"44866.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Novemba 01 2022 4:32 AM"},
{"44896.18957170139", "[$-435]mmmm dd yyyy h:mm AM/PM", "Disemba 01 2022 4:32 AM"},
{"44562.189571759256", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44593.189571759256", "[$-435]mmmmm dd yyyy h:mm AM/PM", "F 01 2022 4:32 AM"},
{"44621.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44652.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "E 01 2022 4:32 AM"},
{"44682.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "M 01 2022 4:32 AM"},
{"44713.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44743.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "J 01 2022 4:32 AM"},
{"44774.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "A 01 2022 4:32 AM"},
{"44805.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "S 01 2022 4:32 AM"},
{"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM aaa", "O 01 2022 4:32 AM Mgq."},
{"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM ddd", "N 01 2022 4:32 AM Bi."},
{"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM dddd", "D 01 2022 4:32 AM ULwesine"},
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "Tuesday, March 19, 2019"},
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"},
{"text_", "General", "text_"},
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000 "},
{"8.0450685976001E+21", "0_);[Red]\\(0\\)", "8045068597600100000000 "},
{"8.0450685976001E-21", "0_);[Red]\\(0\\)", "0 "},
{"8.04506", "0_);[Red]\\(0\\)", "8 "},
{"-0.0450685976001E+21", "0_);[Red]\\(0\\)", "(45068597600100000000)"},
{"-8.0450685976001E+21", "0_);[Red]\\(0\\)", "(8045068597600100000000)"},
{"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"},
{"-8.04506", "0_);[Red]\\(0\\)", "(8)"},
{"-8.04506", "$#,##0.00_);[Red]($#,##0.00)", "($8.05)"},
{"43543.5448726851", `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`, " $43,543.54 "},
{"1234.5678", "0", "1235"},
{"1234.125", "0.00", "1234.13"},
{"1234.5678", "0.00", "1234.57"},
{"1234.5678", "#,##0", "1,235"},
{"1234.5678", "#,##0.00", "1,234.57"},
{"1234.5678", "0%", "123457%"},
{"1234.5678", "#,##0 ;(#,##0)", "1,235 "},
{"1234.5678", "#,##0 ;[red](#,##0)", "1,235 "},
{"1234.5678", "#,##0.00;(#,##0.00)", "1,234.57"},
{"1234.5678", "#,##0.00;[red](#,##0.00)", "1,234.57"},
{"1234.5678", "#", "1235"},
{"1234.5678", "#0", "1235"},
{"1234.5678", "##", "1235"},
{"1234.5678", "00000.00#", "01234.568"},
{"1234.5678", "00000####", "000001235"},
{"1234.5678", "00000######", "000001235"},
{"-1234.5678", "0.00", "-1234.57"},
{"-1234.5678", "0.00;-0.00", "-1234.57"},
{"-1234.5678", "0.00%%", "-12345678.00%%"},
{"2.1", "mmss.0000", "2400.000"},
{"0.007", "[h]:mm:ss.0", "0:10:04.8"},
{"0.007", "[h]:mm:ss.00", "0:10:04.80"},
{"0.007", "[h]:mm:ss.000", "0:10:04.800"},
{"0.007", "[h]:mm:ss.0000", "0:10:04.800"},
{"0.3270833333", "[h]:mm", "7:51"},
{"0.5347222222", "[h]:mm", "12:50"},
{"0.5833333333", "[h]:mm", "14:00"},
{"0.5833333333", "hh", "14"},
{"123", "[h]:mm,:ss.0", "2952:00,:00.0"},
{"123", "yy-.dd", "00-.02"},
{"123", "[DBNum1][$-804]yyyy\"年\"m\"月\";@", "\u4E00\u4E5D\u25CB\u25CB\u5E74\u4E94\u6708"},
{"123", "[DBNum2][$-804]yyyy\"年\"m\"月\";@", "\u58F9\u7396\u96F6\u96F6\u5E74\u4F0D\u6708"},
{"123", "[DBNum3][$-804]yyyy\"年\"m\"月\";@", "\uFF11\uFF19\uFF10\uFF10\u5E74\uFF15\u6708"},
{"1234567890", "[DBNum1][$-804]0.00", "\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D\u25CB.\u25CB\u25CB"},
{"1234567890", "[DBNum2][$-804]0.00", "\u58F9\u8D30\u53C1\u8086\u4F0D\u9646\u67D2\u634C\u7396\u96F6.\u96F6\u96F6"},
{"1234567890", "[DBNum3][$-804]0.00", "\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19\uFF10.\uFF10\uFF10"},
{"1234.5678", "0.00###", "1234.5678"},
{"1234.5678", "00000.00###", "01234.5678"},
{"-1234.5678", "00000.00###;;", ""},
{"1234.5678", "0.00000", "1234.56780"},
{"8.8888666665555487", "0.00000", "8.88887"},
{"8.8888666665555493e+19", "#,000.00", "88,888,666,665,555,500,000.00"},
{"8.8888666665555493e+19", "0.00000", "88888666665555500000.00000"},
{"37947.7500001", "0.00000000E+00", "3.79477500E+04"},
{"2312312321.1231198", "0.00E+00", "2.31E+09"},
{"3.2234623764278598E+33", "0.00E+00", "3.22E+33"},
{"1.234E-16", "0.00000000000000000000", "0.00000000000000012340"},
{"1.234E-16", "0.000000000000000000", "0.000000000000000123"},
{"1.234E-16", "0.000000000000000000%", "0.000000000000012340%"},
{"1.234E-16", "0.000000000000000000%%%%", "0.000000000000012340%"},
{"-123.4567", "# ?/?", "-123 1/2"},
{"123.4567", "# ??/??", "123 37/81"},
{"123.4567", "#\\ ???/???", "123 58/127"},
{"123.4567", "#\\ ?/2", "123 1/2"},
{"123.4567", "#\\ ?/4", "123 2/4"},
{"123.4567", "#\\ ?/8", "123 4/8"},
{"123.4567", "#\\ ?/16", "123 7/16"},
{"123.4567", "#\\ ?/10", "123 5/10"},
{"-123.4567", "#\\ ?/100", "-123 46/100"},
{"123.4567", "#\\ ?/1000", "123 457/1000"},
{"1234.5678", "[$$-409]#,##0.00", "$1,234.57"},
{"123", "[$x.-unknown]#,##0.00", "x.123.00"},
{"123", "[$x.-unknown]MM/DD/YYYY", "x.05/02/1900"},
{"1234.5678", "0.0xxx00", "1234.5xxx68"},
{"80145.899999999994", "[$¥-8004]\" \"#\" \"####\"\"", "¥ 8 0146"},
{"1", "?", "1"},
{"1", "???", " 1"},
{"0", "# ?/?", "0 "},
{"0", "# ??/??", "0 "},
{"0", "# ??/???", "0 "},
{"31.69", "?#???", " 32"},
{"314159.26535", "#,### ??/??", "314,159 13/49"},
{"314159.26535", "# ??? ????/??", "314 159 13/49"},
{"-3.14159265358979323", "# ?/?", "-3 1/7"},
{"-3.14159265358979323", "# ??/??", "-3 1/7 "},
{"-3.14159265358979323", "# ???/???", "-3 16/113"},
{"5.81999", `???? ????`, " 6"},
{"4.6", `# #### ####`, " 5"},
// Unsupported number format
{"37947.7500001", "0.00000000E+000", "37947.7500001"},
{"123", "[DBNum4][$-804]yyyy\"年\"m\"月\";@", "123"},
// Invalid number format
{"123", "x0.00s", "123"},
{"123", "[h]:m00m:ss", "123"},
{"123", "yy-00dd", "123"},
{"123", "yy-##dd", "123"},
{"123", "xx[h]:mm,:ss.0xx", "xx2952:00,:00.0xx"},
{"-123", "x0.00s", "-123"},
{"-1234.5678", ";E+;", "-1234.5678"},
{"1234.5678", "E+;", "1234.5678"},
{"1234.5678", "00000.00###s", "1234.5678"},
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
} {
result := format(item[0], item[1], false, CellTypeNumber, nil)
assert.Equal(t, item[2], result, item)
}
// Test format number with specified date and time format code
for _, item := range [][]string{
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"},
{"43543.503206018519", "[$-x-sysdate]dddd, mmmm dd, yyyy", "2019年3月19日"},
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"},
{"43543.503206018519", "[$-x-systime]h:mm:ss AM/PM", "12:04:37"},
} {
result := format(item[0], item[1], false, CellTypeNumber, &Options{
ShortDatePattern: "yyyy/m/d",
LongDatePattern: "yyyy\"年\"M\"月\"d\"日\"",
LongTimePattern: "H:mm:ss",
})
assert.Equal(t, item[2], result, item)
}
// Test format number with string data type cell value
for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} {
for _, item := range [][]string{
{"1234.5678", "General", "1234.5678"},
{"1234.5678", "yyyy\"年\"m\"月\"d\"日\";@", "1234.5678"},
{"1234.5678", "h\"时\"mm\"分\"ss\"秒\";@", "1234.5678"},
{"1234.5678", "\"¥\"#,##0.00_);\\(\"¥\"#,##0.00\\)", "1234.5678"},
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
{"1234.5678", "\"text\"@", "text1234.5678"},
} {
result := format(item[0], item[1], false, cellType, nil)
assert.Equal(t, item[2], result, item)
}
}
nf := numberFormat{}
changeNumFmtCode, err := nf.currencyLanguageHandler(nfp.Token{Parts: []nfp.Part{{}}})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
assert.False(t, changeNumFmtCode)
}
func BenchmarkNumFmtPlaceHolder(b *testing.B) {
items := [][]string{
{"1", "?", "1"},
{"1", "???", " 1"},
{"0", "# ?/?", "0 "},
{"0", "# ??/??", "0 "},
{"0", "# ??/???", "0 "},
{"31.69", "?#???", " 32"},
{"314159.26535", "#,### ??/??", "314,159 13/49"},
{"314159.26535", "# ??? ????/??", "314 159 13/49"},
{"-3.14159265358979323", "# ?/?", "-3 1/7"},
{"-3.14159265358979323", "# ??/??", "-3 1/7 "},
{"123.4567", "#\\ ?/10", "123 5/10"},
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, item := range items {
_ = format(item[0], item[1], false, CellTypeNumber, nil)
}
}
}
================================================
FILE: picture.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"image"
"io"
"math"
"os"
"path"
"path/filepath"
"strconv"
"strings"
)
// PictureInsertType defines the type of the picture has been inserted into the
// worksheet.
type PictureInsertType byte
// Insert picture types.
const (
PictureInsertTypePlaceOverCells PictureInsertType = iota
PictureInsertTypePlaceInCell
PictureInsertTypeIMAGE
PictureInsertTypeDISPIMG
)
// parseGraphicOptions provides a function to parse the format settings of
// the picture with default value.
func (opts *GraphicOptions) parseGraphicOptions(defaults *GraphicOptions) (*GraphicOptions, error) {
if opts == nil {
return defaults, nil
}
if countUTF16String(opts.AltText) > MaxGraphicAltTextLength {
return defaults, ErrMaxGraphicAltTextLength
}
if countUTF16String(opts.Name) > MaxGraphicNameLength {
return defaults, ErrMaxGraphicNameLength
}
if opts.PrintObject == nil {
opts.PrintObject = boolPtr(true)
}
if opts.Locked == nil {
opts.Locked = boolPtr(true)
}
if opts.ScaleX == 0 {
opts.ScaleX = defaultDrawingScale
}
if opts.ScaleY == 0 {
opts.ScaleY = defaultDrawingScale
}
if opts.Positioning != "" && inStrSlice(supportedPositioning, opts.Positioning, true) == -1 {
return defaults, newInvalidOptionalValue("Positioning", opts.Positioning, supportedPositioning)
}
return opts, nil
}
// parsePictureOptions provides a function to parse the picture options with
// default value.
func parsePictureOptions(pic *Picture) (*GraphicOptions, error) {
if pic.InsertType != PictureInsertTypePlaceOverCells {
return nil, ErrParameterInvalid
}
return pic.Format.parseGraphicOptions(&GraphicOptions{
PrintObject: boolPtr(true),
Locked: boolPtr(true),
ScaleX: defaultDrawingScale,
ScaleY: defaultDrawingScale,
})
}
// AddPicture provides the method to add picture in a sheet by given picture
// format set (such as offset, scale, aspect ratio setting and print settings)
// and file path, supported image types: BMP, EMF, EMZ, GIF, ICO, JPEG, JPG,
// PNG, SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency-safe. Note
// that this function only supports adding pictures placed over the cells
// currently, and doesn't support adding pictures placed in cells or creating
// the Kingsoft WPS Office embedded image cells. For example:
//
// package main
//
// import (
// "fmt"
// _ "image/gif"
// _ "image/jpeg"
// _ "image/png"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// // Insert a picture.
// if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil {
// fmt.Println(err)
// return
// }
// // Insert a picture scaling in the cell with location hyperlink.
// enable := true
// if err := f.AddPicture("Sheet1", "D2", "image.png",
// &excelize.GraphicOptions{
// ScaleX: 0.5,
// ScaleY: 0.5,
// Hyperlink: "#Sheet2!D8",
// HyperlinkType: "Location",
// },
// ); err != nil {
// fmt.Println(err)
// return
// }
// // Insert a picture offset in the cell with external hyperlink, printing and positioning support.
// if err := f.AddPicture("Sheet1", "H2", "image.gif",
// &excelize.GraphicOptions{
// PrintObject: &enable,
// LockAspectRatio: false,
// OffsetX: 15,
// OffsetY: 10,
// Hyperlink: "https://github.com/xuri/excelize",
// HyperlinkType: "External",
// Positioning: "oneCell",
// },
// ); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
//
// The optional parameter "AltText" is used to add alternative text to a graph
// object.
//
// The optional parameter "PrintObject" indicates whether the graph object is
// printed when the worksheet is printed, the default value of that is 'true'.
//
// The optional parameter "Locked" indicates whether lock the graph object.
// Locking an object has no effect unless the sheet is protected.
//
// The optional parameter "LockAspectRatio" indicates whether lock aspect ratio
// for the graph object, the default value of that is 'false'.
//
// The optional parameter "AutoFit" specifies if you make graph object size
// auto-fits the cell, the default value of that is 'false'.
//
// The optional parameter "AutoFitIgnoreAspect" specifies if fill the cell with
// the image and ignore its aspect ratio, the default value of that is 'false'.
// This option only works when the "AutoFit" is enabled.
//
// The optional parameter "OffsetX" specifies the horizontal offset of the graph
// object with the cell, the default value of that is 0.
//
// The optional parameter "OffsetY" specifies the vertical offset of the graph
// object with the cell, the default value of that is 0.
//
// The optional parameter "ScaleX" specifies the horizontal scale of graph
// object. The value of ScaleX must be a floating-point number greater than 0
// with a precision of two decimal places. The default value of that is 1.0
// which presents 100%.
//
// The optional parameter "ScaleY" specifies the vertical scale of graph object.
// The value of ScaleY must be a floating-point number greater than 0 with a
// precision of two decimal places. The default value of that is 1.0 which
// presents 100%.
//
// The optional parameter "Hyperlink" specifies the hyperlink of the graph
// object.
//
// The optional parameter "HyperlinkType" defines two types of
// hyperlink "External" for website or "Location" for moving to one of the
// cells in this workbook. When the "HyperlinkType" is "Location",
// coordinates need to start with "#".
//
// The optional parameter "Positioning" defines 3 types of the position of a
// graph object in a spreadsheet: "oneCell" (Move but don't size with
// cells), "twoCell" (Move and size with cells), and "absolute" (Don't move or
// size with cells). If you don't set this parameter, the default positioning
// is to move and size with cells.
func (f *File) AddPicture(sheet, cell, name string, opts *GraphicOptions) error {
var err error
// Check picture exists first.
if _, err = os.Stat(name); os.IsNotExist(err) {
return err
}
ext, ok := supportedImageTypes[strings.ToLower(path.Ext(name))]
if !ok {
return ErrImgExt
}
file, _ := os.ReadFile(filepath.Clean(name))
return f.AddPictureFromBytes(sheet, cell, &Picture{Extension: ext, File: file, Format: opts})
}
// AddPictureFromBytes provides the method to add picture in a sheet by given
// picture format set (such as offset, scale, aspect ratio setting and print
// settings), file base name, extension name and file bytes, supported image
// types: EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. Note
// that this function only supports adding pictures placed over the cells
// currently, and doesn't support adding pictures placed in cells or creating
// the Kingsoft WPS Office embedded image cells. For example:
//
// package main
//
// import (
// "fmt"
// _ "image/jpeg"
// "os"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// file, err := os.ReadFile("image.jpg")
// if err != nil {
// fmt.Println(err)
// return
// }
// if err := f.AddPictureFromBytes("Sheet1", "A2", &excelize.Picture{
// Extension: ".jpg",
// File: file,
// Format: &excelize.GraphicOptions{AltText: "Excel Logo"},
// }); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
var drawingHyperlinkRID int
var hyperlinkType string
ext, ok := supportedImageTypes[strings.ToLower(pic.Extension)]
if !ok {
return ErrImgExt
}
options, err := parsePictureOptions(pic)
if err != nil {
return err
}
img, _, err := image.DecodeConfig(bytes.NewReader(pic.File))
if err != nil {
return err
}
// Read sheet data
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
drawingID := f.countDrawings() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
mediaStr := ".." + strings.TrimPrefix(f.addMedia(pic.File, ext), "xl")
var drawingRID int
if rels, _ := f.relsReader(drawingRels); rels != nil {
for _, rel := range rels.Relationships {
if rel.Type == SourceRelationshipImage && rel.Target == mediaStr {
drawingRID, _ = strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
break
}
}
}
if drawingRID == 0 {
drawingRID = f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
}
// Add picture with hyperlink.
if options.Hyperlink != "" && options.HyperlinkType != "" {
if options.HyperlinkType == "External" {
hyperlinkType = options.HyperlinkType
}
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType)
}
ws.mu.Unlock()
err = f.addDrawingPicture(sheet, drawingXML, cell, ext, drawingRID, drawingHyperlinkRID, img, options)
if err != nil {
return err
}
if err = f.addContentTypePart(drawingID, "drawings"); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
return err
}
// addSheetLegacyDrawing provides a function to add legacy drawing element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
ws, _ := f.workSheetReader(sheet)
ws.LegacyDrawing = &xlsxLegacyDrawing{
RID: "rId" + strconv.Itoa(rID),
}
}
// addSheetLegacyDrawingHF provides a function to add legacy drawing
// header/footer element to xl/worksheets/sheet%d.xml by given
// worksheet name and relationship index.
func (f *File) addSheetLegacyDrawingHF(sheet string, rID int) {
ws, _ := f.workSheetReader(sheet)
ws.LegacyDrawingHF = &xlsxLegacyDrawingHF{
RID: "rId" + strconv.Itoa(rID),
}
}
// addSheetDrawing provides a function to add drawing element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetDrawing(sheet string, rID int) {
ws, _ := f.workSheetReader(sheet)
ws.Drawing = &xlsxDrawing{
RID: "rId" + strconv.Itoa(rID),
}
}
// addSheetPicture provides a function to add picture element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetPicture(sheet string, rID int) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.Picture = &xlsxPicture{
RID: "rId" + strconv.Itoa(rID),
}
return err
}
// countDrawings provides a function to get drawing files count storage in the
// folder xl/drawings.
func (f *File) countDrawings() int {
drawings := map[string]struct{}{}
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/drawing") {
drawings[k.(string)] = struct{}{}
}
return true
})
f.Drawings.Range(func(rel, value interface{}) bool {
if strings.Contains(rel.(string), "xl/drawings/drawing") {
drawings[rel.(string)] = struct{}{}
}
return true
})
return len(drawings)
}
// addDrawingPicture provides a function to add picture by given sheet,
// drawingXML, cell, file name, width, height relationship index and format
// sets.
func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyperlinkRID int, img image.Config, opts *GraphicOptions) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
if opts.Positioning != "" && inStrSlice(supportedPositioning, opts.Positioning, true) == -1 {
return newInvalidOptionalValue("Positioning", opts.Positioning, supportedPositioning)
}
width, height := img.Width, img.Height
if opts.AutoFit {
if width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts); err != nil {
return err
}
} else {
width = int(float64(width) * opts.ScaleX)
height = int(float64(height) * opts.ScaleY)
}
colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, col, row, width, height, opts)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
cellAnchor := xdrCellAnchor{}
from := xlsxFrom{}
from.Col = colStart
from.ColOff = x1 * EMU
from.Row = rowStart
from.RowOff = y1 * EMU
cellAnchor.From = &from
if opts.Positioning != "oneCell" {
to := xlsxTo{}
to.Col = colEnd
to.ColOff = x2 * EMU
to.Row = rowEnd
to.RowOff = y2 * EMU
cellAnchor.To = &to
cellAnchor.EditAs = opts.Positioning
}
pic := xlsxPic{}
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio
pic.NvPicPr.CNvPr.ID = cNvPrID
pic.NvPicPr.CNvPr.Descr = opts.AltText
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
if len(opts.Name) > 0 {
pic.NvPicPr.CNvPr.Name = opts.Name
}
if hyperlinkRID != 0 {
pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{
R: SourceRelationship.Value,
RID: "rId" + strconv.Itoa(hyperlinkRID),
}
}
pic.BlipFill.Blip.R = SourceRelationship.Value
pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
if ext == ".svg" {
pic.BlipFill.Blip.ExtList = &xlsxEGOfficeArtExtensionList{
Ext: []xlsxCTOfficeArtExtension{
{
URI: ExtURISVG,
SVGBlip: xlsxCTSVGBlip{
XMLNSaAVG: NameSpaceDrawing2016SVG.Value,
Embed: pic.BlipFill.Blip.Embed,
},
},
},
}
}
pic.SpPr.PrstGeom.Prst = "rect"
pic.SpPr.Xfrm.Ext.Cx = width * EMU
pic.SpPr.Xfrm.Ext.Cy = height * EMU
if opts.Positioning == "oneCell" {
cx := x2 * EMU
cy := y2 * EMU
cellAnchor.Ext = &xlsxPositiveSize2D{
Cx: cx,
Cy: cy,
}
pic.SpPr.Xfrm.Ext.Cx = cx
pic.SpPr.Xfrm.Ext.Cy = cy
}
cellAnchor.Pic = &pic
cellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: *opts.PrintObject,
}
content.mu.Lock()
defer content.mu.Unlock()
if opts.Positioning == "oneCell" {
content.OneCellAnchor = append(content.OneCellAnchor, &cellAnchor)
} else {
content.TwoCellAnchor = append(content.TwoCellAnchor, &cellAnchor)
}
f.Drawings.Store(drawingXML, content)
return err
}
// countMedia provides a function to get media files count storage in the
// folder xl/media/image.
func (f *File) countMedia() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/media/image") {
count++
}
return true
})
return count
}
// addMedia provides a function to add a picture into folder xl/media/image by
// given file and extension name. Duplicate images are only actually stored once
// and drawings that use it will reference the same image.
func (f *File) addMedia(file []byte, ext string) string {
count := f.countMedia()
var name string
f.Pkg.Range(func(k, existing interface{}) bool {
if !strings.HasPrefix(k.(string), "xl/media/image") {
return true
}
if bytes.Equal(file, existing.([]byte)) {
name = k.(string)
return false
}
return true
})
if name != "" {
return name
}
media := "xl/media/image" + strconv.Itoa(count+1) + ext
f.Pkg.Store(media, file)
return media
}
// GetPictures provides a function to get picture meta info and raw content
// embed in spreadsheet by given worksheet and cell name. This function
// returns the image contents as []byte data types. This function is
// concurrency safe. Note that this function currently does not support
// retrieving all properties from the image's Format property, and the value of
// the ScaleX and ScaleY property is a floating-point number greater than 0 with
// a precision of two decimal places. For example:
//
// f, err := excelize.OpenFile("Book1.xlsx")
// if err != nil {
// fmt.Println(err)
// return
// }
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// pics, err := f.GetPictures("Sheet1", "A2")
// if err != nil {
// fmt.Println(err)
// }
// for idx, pic := range pics {
// name := fmt.Sprintf("image%d%s", idx+1, pic.Extension)
// if err := os.WriteFile(name, pic.File, 0644); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return nil, err
}
col--
row--
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return nil, err
}
f.mu.Unlock()
if ws.Drawing == nil {
return f.getCellImages(sheet, cell)
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
imgs, err := f.getCellImages(sheet, cell)
if err != nil {
return nil, err
}
pics, err := f.getPicture(row, col, drawingXML, drawingRelationships)
if err != nil {
return nil, err
}
return append(imgs, pics...), err
}
// GetPictureCells returns all picture cell references in a worksheet by a
// specific worksheet name.
func (f *File) GetPictureCells(sheet string) ([]string, error) {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return nil, err
}
f.mu.Unlock()
if ws.Drawing == nil {
return f.getImageCells(sheet)
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
embeddedImageCells, err := f.getImageCells(sheet)
if err != nil {
return nil, err
}
imageCells, err := f.getPictureCells(drawingXML, drawingRelationships)
if err != nil {
return nil, err
}
return append(embeddedImageCells, imageCells...), err
}
// DeletePicture provides a function to delete all pictures in a cell by given
// worksheet name and cell reference.
func (f *File) DeletePicture(sheet, cell string) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
col--
row--
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.Drawing == nil {
return err
}
drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
drawingRels := "xl/drawings/_rels/" + filepath.Base(drawingXML) + ".rels"
rIDs, err := f.deleteDrawing(col, row, drawingXML, "Pic")
if err != nil {
return err
}
for _, rID := range rIDs {
rels := f.getDrawingRelationships(drawingRels, rID)
if rels == nil {
return err
}
var used bool
checkPicRef := func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/_rels/drawing") {
if k.(string) == drawingRels {
return true
}
r, err := f.relsReader(k.(string))
if err != nil {
return true
}
for _, rel := range r.Relationships {
if rel.Type == SourceRelationshipImage &&
filepath.Base(rel.Target) == filepath.Base(rels.Target) {
used = true
}
}
}
return true
}
f.Relationships.Range(checkPicRef)
f.Pkg.Range(checkPicRef)
if !used {
f.Pkg.Delete(strings.ReplaceAll(rels.Target, "../", "xl/"))
}
f.deleteDrawingRels(drawingRels, rID)
}
return err
}
// getPicture provides a function to get picture base name and raw content
// embed in spreadsheet by given coordinates and drawing relationships.
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) {
var wsDr *xlsxWsDr
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return
}
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
cond := func(from *xlsxFrom) bool { return from.Col == col && from.Row == row }
cond2 := func(from *decodeFrom) bool { return from.Col == col && from.Row == row }
cb := func(a *xdrCellAnchor, r *xlsxRelationship, drawingRelationships string) {
if pic := f.extractPictureFromAnchor(drawingRelationships, a, r); pic != nil {
pics = append(pics, *pic)
}
}
cb2 := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) {
if pic := f.extractPictureFromDecodeAnchor(drawingRelationships, a, r); pic != nil {
pics = append(pics, *pic)
}
}
for _, anchor := range wsDr.TwoCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
for _, anchor := range wsDr.OneCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
return
}
// extractPictureFromAnchor extracts picture data from a cell anchor and
// relationship.
func (f *File) extractPictureFromAnchor(drawingRelationships string, a *xdrCellAnchor, r *xlsxRelationship) *Picture {
var (
cx, cy int
pic *Picture
)
if buffer, _ := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); buffer != nil {
pic = &Picture{
Extension: filepath.Ext(r.Target),
File: buffer.([]byte),
Format: &GraphicOptions{ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale},
}
if a.ClientData != nil {
pic.Format.Locked = &a.ClientData.FLocksWithSheet
pic.Format.PrintObject = &a.ClientData.FPrintsWithSheet
}
if a.To == nil {
pic.Format.Positioning = "oneCell"
}
if a.Pic != nil {
cx, cy = a.Pic.SpPr.Xfrm.Ext.Cx, a.Pic.SpPr.Xfrm.Ext.Cy
if a.From != nil {
pic.Format.OffsetX = int(a.From.ColOff / EMU)
pic.Format.OffsetY = int(a.From.RowOff / EMU)
}
pic.Format.LockAspectRatio = a.Pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
pic.Format.Name = a.Pic.NvPicPr.CNvPr.Name
if a.Pic.NvPicPr.CNvPr.HlinkClick != nil {
if drawRel := f.getDrawingRelationships(drawingRelationships, a.Pic.NvPicPr.CNvPr.HlinkClick.RID); drawRel != nil {
pic.Format.Hyperlink = drawRel.Target
pic.Format.HyperlinkType = "Location"
if drawRel.TargetMode == "External" {
pic.Format.HyperlinkType = "External"
}
}
}
}
f.calculatePictureScale(pic, cx, cy)
}
return pic
}
// calculatePictureScale calculates and sets the scale factors for a picture.
func (f *File) calculatePictureScale(pic *Picture, cx, cy int) {
imgCfg, _, err := image.DecodeConfig(bytes.NewReader(pic.File))
if err != nil || imgCfg.Width <= 0 || imgCfg.Height <= 0 || cx <= 0 || cy <= 0 {
return
}
pic.Format.ScaleX = math.Round(float64(cx)/float64(EMU)/float64(imgCfg.Width)*100) / 100
pic.Format.ScaleY = math.Round(float64(cy)/float64(EMU)/float64(imgCfg.Height)*100) / 100
}
// extractPictureFromDecodeAnchor extracts picture data from a decoded cell
// anchor and relationship.
func (f *File) extractPictureFromDecodeAnchor(drawingRelationships string, a *decodeCellAnchor, r *xlsxRelationship) *Picture {
var (
cx, cy int
pic *Picture
target string
)
if strings.HasPrefix(r.Target, "/") {
target = strings.TrimPrefix(r.Target, "/")
} else {
target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))
}
if buffer, _ := f.Pkg.Load(target); buffer != nil {
pic = &Picture{
Extension: filepath.Ext(target),
File: buffer.([]byte),
Format: &GraphicOptions{ScaleX: defaultDrawingScale, ScaleY: defaultDrawingScale},
}
if a.ClientData != nil {
pic.Format.Locked = &a.ClientData.FLocksWithSheet
pic.Format.PrintObject = &a.ClientData.FPrintsWithSheet
}
if a.To == nil {
pic.Format.Positioning = "oneCell"
}
if a.Pic != nil {
cx, cy = a.Pic.SpPr.Xfrm.Ext.Cx, a.Pic.SpPr.Xfrm.Ext.Cy
if a.From != nil {
pic.Format.OffsetX = int(a.From.ColOff / EMU)
pic.Format.OffsetY = int(a.From.RowOff / EMU)
}
pic.Format.LockAspectRatio = a.Pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
pic.Format.Name = a.Pic.NvPicPr.CNvPr.Name
if a.Pic.NvPicPr.CNvPr.HlinkClick != nil {
if drawRel := f.getDrawingRelationships(drawingRelationships, a.Pic.NvPicPr.CNvPr.HlinkClick.RID); drawRel != nil {
pic.Format.Hyperlink = drawRel.Target
pic.Format.HyperlinkType = "Location"
if drawRel.TargetMode == "External" {
pic.Format.HyperlinkType = "External"
}
}
}
}
f.calculatePictureScale(pic, cx, cy)
}
return pic
}
// extractCellAnchor extract drawing object from cell anchor by giving drawing
// cell anchor, drawing relationships part path, conditional and callback
// function.
func (f *File) extractCellAnchor(anchor *xdrCellAnchor, drawingRelationships string,
cond func(from *xlsxFrom) bool,
cb func(anchor *xdrCellAnchor, rels *xlsxRelationship, drawingRelationships string),
cond2 func(from *decodeFrom) bool,
cb2 func(anchor *decodeCellAnchor, rels *xlsxRelationship, drawingRelationships string),
) {
var drawRel *xlsxRelationship
if anchor.GraphicFrame == "" {
if anchor.From != nil && anchor.Pic != nil {
if cond(anchor.From) {
if drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
cb(anchor, drawRel, drawingRelationships)
}
}
}
}
return
}
f.extractDecodeCellAnchor(anchor, drawingRelationships, cond2, cb2)
}
// extractDecodeCellAnchor extract drawing object from cell anchor by giving
// decoded drawing cell anchor, drawing relationships part path, conditional and
// callback function.
func (f *File) extractDecodeCellAnchor(anchor *xdrCellAnchor, drawingRelationships string,
cond func(from *decodeFrom) bool, cb func(anchor *decodeCellAnchor, rels *xlsxRelationship, drawingRelationships string),
) {
var (
drawRel *xlsxRelationship
deCellAnchor = new(decodeCellAnchor)
)
_ = f.xmlNewDecoder(strings.NewReader("" + anchor.GraphicFrame + "")).Decode(&deCellAnchor)
if deCellAnchor.From != nil && deCellAnchor.Pic != nil {
if cond(deCellAnchor.From) {
if drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
cb(deCellAnchor, drawRel, drawingRelationships)
}
}
}
}
}
// getDrawingRelationships provides a function to get drawing relationships
// from xl/drawings/_rels/drawing%s.xml.rels by given file name and
// relationship ID.
func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship {
if drawingRels, _ := f.relsReader(rels); drawingRels != nil {
drawingRels.mu.Lock()
defer drawingRels.mu.Unlock()
for _, v := range drawingRels.Relationships {
if v.ID == rID {
return &v
}
}
}
return nil
}
// drawingsWriter provides a function to save xl/drawings/drawing%d.xml after
// serialize structure.
func (f *File) drawingsWriter() {
f.Drawings.Range(func(path, d interface{}) bool {
if d != nil {
v, _ := xml.Marshal(d.(*xlsxWsDr))
f.saveFileList(path.(string), v)
}
return true
})
}
// drawingResize calculate the height and width after resizing.
func (f *File) drawingResize(sheet, cell string, width, height float64, opts *GraphicOptions) (w, h, c, r int, err error) {
var mergeCells []MergeCell
mergeCells, err = f.GetMergeCells(sheet)
if err != nil {
return
}
var rng []int
var inMergeCell bool
if c, r, err = CellNameToCoordinates(cell); err != nil {
return
}
cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r)
for _, mergeCell := range mergeCells {
if inMergeCell {
continue
}
if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err == nil {
rng, _ = cellRefsToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
_ = sortCoordinates(rng)
}
}
if inMergeCell {
cellWidth, cellHeight = 0, 0
c, r = rng[0], rng[1]
for col := rng[0]; col <= rng[2]; col++ {
cellWidth += f.getColWidth(sheet, col)
}
for row := rng[1]; row <= rng[3]; row++ {
cellHeight += f.getRowHeight(sheet, row)
}
}
if float64(cellWidth) < width || float64(cellHeight) < height {
aspWidth := float64(cellWidth) / width
aspHeight := float64(cellHeight) / height
asp := min(aspWidth, aspHeight)
width, height = width*asp, height*asp
}
if opts.AutoFitIgnoreAspect {
width, height = float64(cellWidth), float64(cellHeight)
}
w, h = int(width*opts.ScaleX), int(height*opts.ScaleY)
return
}
// getPictureCells provides a function to get all picture cell references in a
// worksheet by given drawing part path and drawing relationships path.
func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]string, error) {
var (
cells []string
err error
wsDr *xlsxWsDr
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return cells, err
}
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
cond := func(from *xlsxFrom) bool { return true }
cond2 := func(from *decodeFrom) bool { return true }
cb := func(a *xdrCellAnchor, r *xlsxRelationship, drawingRelationships string) {
if _, ok := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); ok {
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
cells = append(cells, cell)
}
}
}
cb2 := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) {
var target string
if strings.HasPrefix(r.Target, "/") {
target = strings.TrimPrefix(r.Target, "/")
} else {
target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))
}
if _, ok := f.Pkg.Load(target); ok {
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
cells = append(cells, cell)
}
}
}
for _, anchor := range wsDr.TwoCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
for _, anchor := range wsDr.OneCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
return cells, err
}
// cellImagesReader provides a function to get the pointer to the structure
// after deserialization of xl/cellimages.xml.
func (f *File) cellImagesReader() (*decodeCellImages, error) {
if f.DecodeCellImages == nil {
f.DecodeCellImages = new(decodeCellImages)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCellImages)))).
Decode(f.DecodeCellImages); err != nil && err != io.EOF {
return f.DecodeCellImages, err
}
}
return f.DecodeCellImages, nil
}
// getImageCells returns all the cell images and the Kingsoft WPS
// Office embedded image cells reference by given worksheet name.
func (f *File) getImageCells(sheet string) ([]string, error) {
var (
err error
cells []string
)
ws, err := f.workSheetReader(sheet)
if err != nil {
return cells, err
}
for _, row := range ws.SheetData.Row {
for _, c := range row.C {
if c.F != nil && c.F.Content != "" &&
strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(c.F.Content, "="), "_xlfn."), "DISPIMG") {
if _, err = f.CalcCellValue(sheet, c.R); err != nil {
return cells, err
}
cells = append(cells, c.R)
}
r, err := f.getImageCellRel(&c, &Picture{Format: &GraphicOptions{}})
if err != nil {
return cells, err
}
if r != nil {
cells = append(cells, c.R)
}
}
}
return cells, err
}
// getRichDataRichValueRel returns relationship of the cell image by given meta
// blocks value.
func (f *File) getRichDataRichValueRel(val string) (*xlsxRelationship, error) {
var r *xlsxRelationship
idx, err := strconv.Atoi(val)
if err != nil {
return r, err
}
richValueRel, err := f.richValueRelReader()
if err != nil {
return r, err
}
if idx >= len(richValueRel.Rels) {
return r, err
}
rID := richValueRel.Rels[idx].ID
if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
return nil, err
}
return r, err
}
// getRichDataWebImagesRel returns relationship of a web image by given meta
// blocks value.
func (f *File) getRichDataWebImagesRel(val string) (*xlsxRelationship, error) {
var r *xlsxRelationship
idx, err := strconv.Atoi(val)
if err != nil {
return r, err
}
richValueWebImages, err := f.richValueWebImageReader()
if err != nil {
return r, err
}
if idx >= len(richValueWebImages.WebImageSrd) {
return r, err
}
rID := richValueWebImages.WebImageSrd[idx].Blip.RID
if r = f.getRichValueWebImageRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
return nil, err
}
return r, err
}
// getImageCellRel returns the cell image relationship.
func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error) {
var r *xlsxRelationship
if c.Vm == nil || c.V != formulaErrorVALUE {
return r, nil
}
metaData, err := f.metadataReader()
if err != nil {
return r, err
}
vmd := metaData.ValueMetadata
if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 {
return r, err
}
richValueIdx := vmd.Bk[*c.Vm-1].Rc[0].V
richValue, err := f.richValueReader()
if err != nil {
return r, err
}
if richValueIdx >= len(richValue.Rv) {
return r, err
}
rv := richValue.Rv[richValueIdx]
rvStructures, err := f.richValueStructuresReader()
if err != nil {
return r, err
}
if rv.S >= len(rvStructures.S) {
return r, err
}
rvStruct := rvStructures.S[rv.S]
if len(rvStruct.K) != len(rv.V) {
return r, err
}
if idx := rvStruct.getRichDataValueIdx("Text"); idx != -1 {
pic.Format.AltText = rv.V[idx]
}
if idx := rvStruct.getRichDataValueIdx("_rvRel:LocalImageIdentifier"); idx != -1 {
pic.InsertType = PictureInsertTypePlaceInCell
return f.getRichDataRichValueRel(rv.V[idx])
}
// cell image inserted by IMAGE formula function
if idx := rvStruct.getRichDataValueIdx("WebImageIdentifier"); idx != -1 {
pic.InsertType = PictureInsertTypeIMAGE
return f.getRichDataWebImagesRel(rv.V[idx])
}
return r, err
}
// getRichDataValueIdx provides a function to get the index of rich data value
// structure by given name and rich data value.
func (s *xlsxRichValueStructure) getRichDataValueIdx(n string) int {
for idx, k := range s.K {
if k.N == n {
return idx
}
}
return -1
}
// getCellImages provides a function to get the cell images and
// the Kingsoft WPS Office embedded cell images by given worksheet name and cell
// reference.
func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
pics, err := f.getDispImages(sheet, cell)
if err != nil {
return pics, err
}
_, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
pic := Picture{Format: &GraphicOptions{}, InsertType: PictureInsertTypePlaceInCell}
r, err := f.getImageCellRel(c, &pic)
if err != nil || r == nil {
return "", true, err
}
pic.Extension = filepath.Ext(r.Target)
if buffer, _ := f.Pkg.Load(strings.TrimPrefix(strings.ReplaceAll(r.Target, "..", "xl"), "/")); buffer != nil {
pic.File = buffer.([]byte)
pics = append(pics, pic)
}
return "", true, nil
})
return pics, err
}
// getDispImages provides a function to get the Kingsoft WPS Office embedded
// cell images by given worksheet name and cell reference.
func (f *File) getDispImages(sheet, cell string) ([]Picture, error) {
formula, err := f.GetCellFormula(sheet, cell)
if err != nil {
return nil, err
}
if !strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(formula, "="), "_xlfn."), "DISPIMG") {
return nil, err
}
imgID, err := f.CalcCellValue(sheet, cell)
if err != nil {
return nil, err
}
cellImages, err := f.cellImagesReader()
if err != nil {
return nil, err
}
rels, err := f.relsReader(defaultXMLPathCellImagesRels)
if rels == nil {
return nil, err
}
var pics []Picture
for _, cellImg := range cellImages.CellImage {
if cellImg.Pic.NvPicPr.CNvPr.Name == imgID {
for _, r := range rels.Relationships {
if r.ID == cellImg.Pic.BlipFill.Blip.Embed {
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}, InsertType: PictureInsertTypeDISPIMG}
if buffer, _ := f.Pkg.Load("xl/" + r.Target); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = cellImg.Pic.NvPicPr.CNvPr.Descr
pic.Format.Name = cellImg.Pic.NvPicPr.CNvPr.Name
pics = append(pics, pic)
}
}
}
}
}
return pics, err
}
================================================
FILE: picture_test.go
================================================
package excelize
import (
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
)
func BenchmarkAddPictureFromBytes(b *testing.B) {
f := NewFile()
imgFile, err := os.ReadFile(filepath.Join("test", "images", "excel.png"))
if err != nil {
b.Error("unable to load image for benchmark")
}
b.ResetTimer()
for i := 1; i <= b.N; i++ {
if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: "Excel"}}); err != nil {
b.Error(err)
}
}
}
func TestAddPicture(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test add picture to worksheet with offset and location hyperlink
opts := []GraphicOptions{
{Name: "Picture 4", Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"},
{Name: "Picture 4", OffsetX: 10, OffsetY: 10, ScaleX: 0.5, ScaleY: 0.5, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell"},
{Name: "Picture 5", OffsetX: 10, OffsetY: 10, ScaleX: 0.88, ScaleY: 0.88, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External"},
{Name: "Picture 4", PrintObject: boolPtr(true), Locked: boolPtr(true), OffsetX: 200, ScaleX: 1, ScaleY: 1, Positioning: "oneCell"},
{Name: "Picture 9", PrintObject: boolPtr(true), Locked: boolPtr(true), AltText: "Excel Logo", LockAspectRatio: true, ScaleX: 1, ScaleY: 1},
}
assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), &opts[0]))
// Test add picture to worksheet with offset, external hyperlink and positioning
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), &opts[1]))
assert.NoError(t, f.AddPicture("Sheet1", "H21", filepath.Join("test", "images", "excel.jpg"), &opts[2]))
// Test add pictures to single cell with offsets
assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"),
&GraphicOptions{Positioning: "oneCell"}))
assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"), &opts[3]))
assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"),
&GraphicOptions{OffsetX: 400, Positioning: "oneCell"}))
assert.NoError(t, f.AddPicture("Sheet2", "K22", filepath.Join("test", "images", "excel.jpg"),
&GraphicOptions{OffsetX: 600, Positioning: "oneCell"}))
file, err := os.ReadFile(filepath.Join("test", "images", "excel.png"))
assert.NoError(t, err)
// Test add picture to worksheet with autofit
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 10, OffsetY: 10, AutoFit: true}))
assert.NoError(t, f.AddPicture("Sheet1", "C30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true, AutoFitIgnoreAspect: true}))
_, err = f.NewSheet("AddPicture")
assert.NoError(t, err)
assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9"))
assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1"))
assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
// Test add picture to worksheet from bytes
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &opts[4]}))
// Test add picture to worksheet from bytes with unsupported insert type
assert.Equal(t, ErrParameterInvalid, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}, InsertType: PictureInsertTypePlaceInCell}))
// Test add picture to worksheet from bytes with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}))
for _, preset := range [][]string{{"Q8", "gif"}, {"Q15", "jpg"}, {"Q22", "tif"}, {"Q28", "bmp"}} {
assert.NoError(t, f.AddPicture("Sheet1", preset[0], filepath.Join("test", "images", fmt.Sprintf("excel.%s", preset[1])), nil))
}
// Test get one cell anchor pictures from worksheet which added pictures with offset
pics, err := f.GetPictures("Sheet2", "K22")
assert.NoError(t, err)
assert.Len(t, pics, 4)
assert.Equal(t, opts[3], *pics[1].Format)
// Test get one cell anchor pictures from worksheet which added pictures with offset and scale
pics, err = f.GetPictures("Sheet1", "F21")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[1], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which added pictures with offset and scale
pics, err = f.GetPictures("Sheet1", "H21")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[2], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which added pictures with alternative text
pics, err = f.GetPictures("Sheet1", "Q1")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[4], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which added pictures with location hyperlink
pics, err = f.GetPictures("Sheet2", "I9")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[0], *pics[0].Format)
// Test write file to given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx")))
assert.NoError(t, f.Close())
// Test get pictures after inserting a new picture from a workbook which contains existing pictures
f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), nil))
pics, err = f.GetPictures("Sheet1", "A30")
assert.NoError(t, err)
assert.Len(t, pics, 2)
// Test get one cell anchor pictures from worksheet which already contains pictures
pics, err = f.GetPictures("Sheet2", "K22")
assert.NoError(t, err)
assert.Len(t, pics, 4)
assert.Equal(t, opts[3], *pics[1].Format)
// Test get one cell anchor pictures from worksheet which already contains pictures with offset and scale
pics, err = f.GetPictures("Sheet1", "F21")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[1], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which already contains pictures
pics, err = f.GetPictures("Sheet1", "H21")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[2], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which already contains pictures with alternative text
pics, err = f.GetPictures("Sheet1", "Q1")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[4], *pics[0].Format)
// Test get two cell anchor pictures from worksheet which already contains pictures with location hyperlink
pics, err = f.GetPictures("Sheet2", "I9")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.Equal(t, opts[0], *pics[0].Format)
// Test get picture cells
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"H21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells)
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
assert.NoError(t, err)
path := "xl/drawings/drawing1.xml"
f.Drawings.Delete(path)
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"H21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28", "F21"}, cells)
// Test get picture cells with unsupported charset
f.Drawings.Delete(path)
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
assert.NoError(t, err)
// Test get picture cells with unsupported charset
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test add picture with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}), "XML syntax error on line 1: invalid UTF-8")
// Test add picture with invalid sheet name
assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), nil), ErrSheetNameInvalid.Error())
}
func TestAddPictureErrors(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test add picture to worksheet with invalid file path
assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), nil))
// Test add picture to worksheet with unsupported file type
assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), nil), ErrImgExt.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", &Picture{Extension: "jpg", File: make([]byte, 1), Format: &GraphicOptions{AltText: "Excel Logo"}}), ErrImgExt.Error())
// Test add picture to worksheet with invalid file data
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", &Picture{Extension: ".jpg", File: make([]byte, 1), Format: &GraphicOptions{AltText: "Excel Logo"}}), image.ErrFormat.Error())
// Test add picture to worksheet with name length exceeds the limit
assert.EqualError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{Name: strings.Repeat("s", MaxGraphicNameLength+1)}), ErrMaxGraphicNameLength.Error())
// Test add picture to worksheet with alt text length exceeds the limit
assert.EqualError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{AltText: strings.Repeat("s", MaxGraphicAltTextLength+1)}), ErrMaxGraphicAltTextLength.Error())
// Test add picture with custom image decoder and encoder
decode := func(r io.Reader) (image.Image, error) { return nil, nil }
decodeConfig := func(r io.Reader) (image.Config, error) { return image.Config{Height: 100, Width: 90}, nil }
for cell, ext := range map[string]string{"Q1": "emf", "Q7": "wmf", "Q13": "emz", "Q19": "wmz"} {
image.RegisterFormat(ext, "", decode, decodeConfig)
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", fmt.Sprintf("excel.%s", ext)), nil))
}
assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &GraphicOptions{ScaleX: 2.8}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx")))
assert.NoError(t, f.Close())
}
func TestGetPicture(t *testing.T) {
f := NewFile()
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
pics, err := f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Len(t, pics[0].File, 4718)
assert.Empty(t, pics[0].Format.AltText)
assert.Equal(t, "Picture 2", pics[0].Format.Name)
assert.Equal(t, PictureInsertTypePlaceOverCells, pics[0].InsertType)
f, err = prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
pics, err = f.GetPictures("Sheet1", "F21")
assert.NoError(t, err)
if !assert.NotEmpty(t, filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension))) || !assert.NotEmpty(t, pics[0].File) ||
!assert.NoError(t, os.WriteFile(filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension)), pics[0].File, 0o644)) {
t.FailNow()
}
// Try to get picture from a worksheet with illegal cell reference
_, err = f.GetPictures("Sheet1", "A")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
// Try to get picture from a worksheet that doesn't contain any images
pics, err = f.GetPictures("Sheet3", "I9")
assert.EqualError(t, err, "sheet Sheet3 does not exist")
assert.Len(t, pics, 0)
// Try to get picture from a cell that doesn't contain an image
pics, err = f.GetPictures("Sheet2", "A2")
assert.NoError(t, err)
assert.Len(t, pics, 0)
// Test get picture with invalid sheet name
_, err = f.GetPictures("Sheet:1", "A2")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8")
f.getDrawingRelationships("", "")
f.getSheetRelationshipsTargetByID("", "")
f.deleteSheetRelationships("", "")
// Try to get picture from a local storage file.
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetPicture.xlsx")))
f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
assert.NoError(t, err)
pics, err = f.GetPictures("Sheet1", "F21")
assert.NoError(t, err)
if !assert.NotEmpty(t, filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension))) || !assert.NotEmpty(t, pics[0].File) ||
!assert.NoError(t, os.WriteFile(filepath.Join("test", fmt.Sprintf("image1%s", pics[0].Extension)), pics[0].File, 0o644)) {
t.FailNow()
}
// Try to get picture from a local storage file that doesn't contain an image
pics, err = f.GetPictures("Sheet1", "F22")
assert.NoError(t, err)
assert.Len(t, pics, 0)
assert.NoError(t, f.Close())
// Try to get picture with one cell anchor
f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing2.xml", []byte(`10151322`))
pics, err = f.GetPictures("Sheet2", "K16")
assert.NoError(t, err)
assert.Len(t, pics, 1)
// Try to get picture cells with one cell anchor
cells, err := f.GetPictureCells("Sheet2")
assert.NoError(t, err)
assert.Equal(t, []string{"K16"}, cells)
// Try to get picture cells with absolute target path in the drawing relationship
rels, err := f.relsReader("xl/drawings/_rels/drawing2.xml.rels")
assert.NoError(t, err)
rels.Relationships[0].Target = "/xl/media/image2.jpeg"
cells, err = f.GetPictureCells("Sheet2")
assert.NoError(t, err)
assert.Equal(t, []string{"K16"}, cells)
// Try to get pictures with absolute target path in the drawing relationship
pics, err = f.GetPictures("Sheet2", "K16")
assert.NoError(t, err)
assert.Len(t, pics, 1)
assert.NoError(t, f.Close())
// Test get picture from none drawing worksheet
f = NewFile()
pics, err = f.GetPictures("Sheet1", "F22")
assert.NoError(t, err)
assert.Len(t, pics, 0)
f, err = prepareTestBook1()
assert.NoError(t, err)
// Test get pictures with unsupported charset
path := "xl/drawings/drawing1.xml"
f.Drawings.Delete(path)
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "F21")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f.Drawings.Delete(path)
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get embedded cell pictures
f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetCellFormula("Sheet1", "F21", "_xlfn.DISPIMG(\"ID_********************************\",1)"))
f.Pkg.Store(defaultXMLPathCellImages, []byte(``))
f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(fmt.Sprintf(``, SourceRelationshipImage)))
pics, err = f.GetPictures("Sheet1", "F21")
assert.NoError(t, err)
assert.Len(t, pics, 2)
assert.Equal(t, "CellImage1", pics[0].Format.AltText)
assert.Equal(t, PictureInsertTypeDISPIMG, pics[0].InsertType)
// Test get embedded cell pictures with invalid formula
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "_xlfn.DISPIMG()"))
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments")
// Test get embedded cell pictures with unsupported charset
f.Relationships.Delete(defaultXMLPathCellImagesRels)
f.Pkg.Store(defaultXMLPathCellImagesRels, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "F21")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f.Pkg.Store(defaultXMLPathCellImages, MacintoshCyrillicCharset)
f.DecodeCellImages = nil
_, err = f.GetPictures("Sheet1", "F21")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestAddDrawingPicture(t *testing.T) {
// Test addDrawingPicture with illegal cell reference
f := NewFile()
opts := &GraphicOptions{PrintObject: boolPtr(true), Locked: boolPtr(false)}
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, image.Config{}, opts), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test addDrawingPicture with invalid positioning types
assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning),
f.addDrawingPicture("sheet1", "", "A1", "", 0, 0, image.Config{}, &GraphicOptions{Positioning: "x"}))
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", 0, 0, image.Config{}, opts), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddPictureFromBytes(t *testing.T) {
f := NewFile()
imgFile, err := os.ReadFile("logo.png")
assert.NoError(t, err, "Unable to load logo for test")
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "A1", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: strings.Repeat("\u4e00", MaxGraphicAltTextLength), Name: strings.Repeat("\u4e00", MaxGraphicNameLength)}}))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "A50", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{AltText: "logo"}}))
imageCount := 0
f.Pkg.Range(func(fileName, v interface{}) bool {
if strings.Contains(fileName.(string), "media/image") {
imageCount++
}
return true
})
assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.")
assert.EqualError(t, f.AddPictureFromBytes("SheetN", "A1", &Picture{Extension: ".png", File: imgFile}), "sheet SheetN does not exist")
// Test add picture from bytes with invalid sheet name
assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", "A1", &Picture{Extension: ".png", File: imgFile}), ErrSheetNameInvalid.Error())
// Test add picture from bytes with invalid positioning types
assert.Equal(t, f.AddPictureFromBytes("Sheet1", "A1", &Picture{Extension: ".png", File: imgFile, Format: &GraphicOptions{Positioning: "x"}}), newInvalidOptionalValue("Positioning", "x", supportedPositioning))
}
func TestDeletePicture(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test delete picture on a worksheet which does not contains any pictures
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
// Add same pictures on different worksheets
assert.NoError(t, f.AddPicture("Sheet1", "F20", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "I20", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet2", "F1", filepath.Join("test", "images", "excel.jpg"), nil))
// Test delete picture on a worksheet, the images should be preserved
assert.NoError(t, f.DeletePicture("Sheet1", "F20"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx")))
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
assert.NoError(t, err)
// Test delete same picture on different worksheet, the images should be removed
assert.NoError(t, f.DeletePicture("Sheet1", "F20"))
assert.NoError(t, f.DeletePicture("Sheet1", "I20"))
assert.NoError(t, f.DeletePicture("Sheet2", "F1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture2.xlsx")))
// Test delete picture on not exists worksheet
assert.EqualError(t, f.DeletePicture("SheetN", "A1"), "sheet SheetN does not exist")
// Test delete picture with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.DeletePicture("Sheet:1", "A1"))
// Test delete picture with invalid coordinates
assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), f.DeletePicture("Sheet1", ""))
assert.NoError(t, f.Close())
// Test delete picture on no chart worksheet
assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1"))
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
assert.NoError(t, err)
// Test delete picture with unsupported charset drawing
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeletePicture("Sheet1", "F10"), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
assert.NoError(t, err)
// Test delete picture with unsupported charset drawing relationships
f.Relationships.Delete("xl/drawings/_rels/drawing1.xml.rels")
f.Pkg.Store("xl/drawings/_rels/drawing1.xml.rels", MacintoshCyrillicCharset)
assert.NoError(t, f.DeletePicture("Sheet2", "F1"))
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
assert.NoError(t, err)
// Test delete picture without drawing relationships
f.Relationships.Delete("xl/drawings/_rels/drawing1.xml.rels")
f.Pkg.Delete("xl/drawings/_rels/drawing1.xml.rels")
assert.NoError(t, f.DeletePicture("Sheet1", "I20"))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, err)
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "G1", filepath.Join("test", "images", "excel.jpg"), nil))
drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml")
assert.True(t, ok)
// Made two picture reference the same drawing relationship ID
drawing.(*xlsxWsDr).TwoCellAnchor[1].Pic.BlipFill.Blip.Embed = "rId1"
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
assert.NoError(t, f.Close())
}
func TestDrawingResize(t *testing.T) {
f := NewFile()
// Test calculate drawing resize on not exists worksheet
_, _, _, _, err := f.drawingResize("SheetN", "A1", 1, 1, nil)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test calculate drawing resize with invalid coordinates
_, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil)
assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err)
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
}
func TestSetContentTypePartRelsExtensions(t *testing.T) {
f := NewFile()
f.ContentTypes = &xlsxTypes{}
assert.NoError(t, f.setContentTypePartRelsExtensions())
// Test set content type part relationships extensions with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypePartRelsExtensions(), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetContentTypePartImageExtensions(t *testing.T) {
f := NewFile()
// Test set content type part image extensions with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypePartImageExtensions(), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetContentTypePartVMLExtensions(t *testing.T) {
f := NewFile()
// Test set content type part VML extensions with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypePartVMLExtensions(), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddContentTypePart(t *testing.T) {
f := NewFile()
// Test add content type part with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.addContentTypePart(0, "unknown"), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetPictureCells(t *testing.T) {
f := NewFile()
// Test get picture cells on a worksheet which not contains any pictures
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Empty(t, cells)
// Test get picture cells on not exists worksheet
_, err = f.GetPictureCells("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
// Test get embedded picture cells
f = NewFile()
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "_xlfn.DISPIMG(\"ID_********************************\",1)"))
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"A2", "A1"}, cells)
// Test get embedded cell pictures with invalid formula
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "_xlfn.DISPIMG()"))
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments")
assert.NoError(t, f.Close())
}
func TestExtractDecodeCellAnchor(t *testing.T) {
f := NewFile()
cond := func(a *decodeFrom) bool { return true }
cb := func(a *decodeCellAnchor, r *xlsxRelationship, drawingRelationships string) {}
f.extractDecodeCellAnchor(&xdrCellAnchor{GraphicFrame: string(MacintoshCyrillicCharset)}, "", cond, cb)
}
func TestGetCellImages(t *testing.T) {
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.getCellImages("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get the cell images
prepareWorkbook := func() *File {
f := NewFile()
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
f.Pkg.Store(defaultXMLMetadata, []byte(``))
f.Pkg.Store(defaultXMLRdRichValue, []byte(`05logo`))
f.Pkg.Store(defaultXMLRdRichValueRel, []byte(``))
f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipImage)))
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``))
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
SheetData: xlsxSheetData{Row: []xlsxRow{
{R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
}},
})
return f
}
f = prepareWorkbook()
pics, err := f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 1, len(pics))
assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType)
assert.Equal(t, "logo", pics[0].Format.AltText)
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"A1"}, cells)
// Test get the cell images without image relationships parts
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink)))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images with unsupported charset rich data rich value relationships
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
f.Pkg.Store(defaultXMLRdRichValueRelRels, MacintoshCyrillicCharset)
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images with unsupported charset rich data rich value
f.Pkg.Store(defaultXMLRdRichValueRel, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get the image cells without block of metadata records
cells, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.Empty(t, cells)
// Test get the cell images with rich data rich value relationships
f.Pkg.Store(defaultXMLMetadata, []byte(``))
f.Pkg.Store(defaultXMLRdRichValueRel, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images with unsupported charset meta data
f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get the cell images without block of metadata records
f.Pkg.Store(defaultXMLMetadata, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
f = prepareWorkbook()
// Test get the cell images with empty image cell rich value
f.Pkg.Store(defaultXMLRdRichValue, []byte(`5logo`))
pics, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
assert.Empty(t, pics)
// Test get the cell images without image cell rich value
f.Pkg.Store(defaultXMLRdRichValue, []byte(`01`))
f.Pkg.Delete(defaultXMLRdRichValueStructure)
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images with unsupported charset rich value
f.Pkg.Store(defaultXMLRdRichValue, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f = prepareWorkbook()
// Test get the cell images with invalid rich value index
f.Pkg.Store(defaultXMLMetadata, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
f = prepareWorkbook()
// Test get the cell images inserted by IMAGE formula function
f.Pkg.Store(defaultXMLRdRichValue, []byte(`0100`))
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``))
f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(`
`))
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink, SourceRelationshipImage)))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 1, len(pics))
assert.Equal(t, PictureInsertTypeIMAGE, pics[0].InsertType)
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images relationships
f.Relationships.Delete(defaultXMLRdRichValueWebImageRels)
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, MacintoshCyrillicCharset)
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images inserted by IMAGE formula function without image part
f.Relationships.Delete(defaultXMLRdRichValueWebImageRels)
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink, SourceRelationshipHyperLink)))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images part
f.Pkg.Store(defaultXMLRdRichValueWebImage, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get the cell images inserted by IMAGE formula function with empty charset web images part
f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images inserted by IMAGE formula function with invalid rich value index
f.Pkg.Store(defaultXMLRdRichValue, []byte(`100`))
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
f = prepareWorkbook()
// Test get the cell images inserted by IMAGE formula function with not matched rich value and structure keys
f.Pkg.Store(defaultXMLRdRichValue, []byte(`0`))
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images inserted by IMAGE formula function without _rvRel:WebImageIdentifier in rich value structure
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(``))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
// Test get the cell images inserted by IMAGE formula function with unsupported charset rich value structures
f.Pkg.Store(defaultXMLRdRichValueStructure, MacintoshCyrillicCharset)
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetImageCells(t *testing.T) {
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.getImageCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
================================================
FILE: pivotTable.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"path/filepath"
"reflect"
"strconv"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// PivotTableOptions directly maps the format settings of the pivot table.
//
// PivotTableStyleName: The built-in pivot table style names
//
// PivotStyleLight1 - PivotStyleLight28
// PivotStyleMedium1 - PivotStyleMedium28
// PivotStyleDark1 - PivotStyleDark28
type PivotTableOptions struct {
pivotTableXML string
pivotCacheXML string
pivotSheetName string
pivotDataRange string
namedDataRange bool
DataRange string
PivotTableRange string
Name string
Rows []PivotTableField
Columns []PivotTableField
Data []PivotTableField
Filter []PivotTableField
RowGrandTotals bool
ColGrandTotals bool
ShowDrill bool
UseAutoFormatting bool
PageOverThenDown bool
MergeItem bool
ClassicLayout bool
CompactData bool
ShowError bool
ShowRowHeaders bool
ShowColHeaders bool
ShowRowStripes bool
ShowColStripes bool
ShowLastColumn bool
FieldPrintTitles bool
ItemPrintTitles bool
PivotTableStyleName string
}
// PivotTableField directly maps the field settings of the pivot table.
//
// Name specifies the name of the data field. Maximum 255 characters
// are allowed in data field name, excess characters will be truncated.
//
// Subtotal specifies the aggregation function that applies to this data
// field. The default value is sum. The possible values for this attribute
// are:
//
// Average
// Count
// CountNums
// Max
// Min
// Product
// StdDev
// StdDevp
// Sum
// Var
// Varp
//
// NumFmt specifies the number format ID of the data field, this filed only
// accepts built-in number format ID and does not support custom number format
// expression currently.
type PivotTableField struct {
Compact bool
Data string
Name string
Outline bool
ShowAll bool
InsertBlankRow bool
Subtotal string
DefaultSubtotal bool
NumFmt int
}
// AddPivotTable provides the method to add pivot table by given pivot table
// options. Note that the same fields can not in Columns, Rows and Filter
// fields at the same time.
//
// For example, create a pivot table on the range reference Sheet1!G2:M34 with
// the range reference Sheet1!A1:E31 as the data source, summarize by sum for
// sales:
//
// package main
//
// import (
// "fmt"
// "math/rand"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// // Create some data in a sheet
// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
// year := []int{2017, 2018, 2019}
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
// region := []string{"East", "West", "North", "South"}
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
// for row := 2; row < 32; row++ {
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
// }
// if err := f.AddPivotTable(&excelize.PivotTableOptions{
// DataRange: "Sheet1!A1:E31",
// PivotTableRange: "Sheet1!G2:M34",
// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
// Filter: []excelize.PivotTableField{{Data: "Region"}},
// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
// RowGrandTotals: true,
// ColGrandTotals: true,
// ShowDrill: true,
// ShowRowHeaders: true,
// ShowColHeaders: true,
// ShowLastColumn: true,
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddPivotTable(opts *PivotTableOptions) error {
// parameter validation
_, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opts)
if err != nil {
return err
}
f.clearCalcCache()
pivotTableID := f.countPivotTables() + 1
pivotCacheID := f.countPivotCache() + 1
sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
opts.pivotTableXML = strings.ReplaceAll(sheetRelationshipsPivotTableXML, "..", "xl")
opts.pivotCacheXML = "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
if err = f.addPivotCache(opts); err != nil {
return err
}
// workbook pivot cache
workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, strings.TrimPrefix(opts.pivotCacheXML, "xl/"), "")
cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
// rId not used
_ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
if err = f.addPivotTable(cacheID, pivotTableID, opts); err != nil {
return err
}
pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil {
return err
}
return f.addContentTypePart(pivotCacheID, "pivotCache")
}
// parseFormatPivotTableSet provides a function to validate pivot table
// properties.
func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet, string, error) {
if opts == nil {
return nil, "", ErrParameterRequired
}
pivotTableSheetName, _, err := f.adjustRange(opts.PivotTableRange)
if err != nil {
return nil, "", newPivotTableRangeError(err.Error())
}
if countUTF16String(opts.Name) > MaxFieldLength {
return nil, "", ErrNameLength
}
opts.pivotSheetName = pivotTableSheetName
if err = f.getPivotTableDataRange(opts); err != nil {
return nil, "", err
}
dataSheetName, _, err := f.adjustRange(opts.pivotDataRange)
if err != nil {
return nil, "", newPivotTableDataRangeError(err.Error())
}
dataSheet, err := f.workSheetReader(dataSheetName)
if err != nil {
return dataSheet, "", err
}
pivotTableSheetPath, ok := f.getSheetXMLPath(pivotTableSheetName)
if !ok {
return dataSheet, pivotTableSheetPath, ErrSheetNotExist{pivotTableSheetName}
}
if opts.CompactData && opts.ClassicLayout {
return nil, "", ErrPivotTableClassicLayout
}
var colDataFields, rowDataFields []string
for _, f := range opts.Filter {
if inPivotTableField(opts.Columns, f.Data) != -1 {
colDataFields = append(colDataFields, f.Data)
}
if inPivotTableField(opts.Rows, f.Data) != -1 {
rowDataFields = append(rowDataFields, f.Data)
}
}
if len(colDataFields) > 0 {
return dataSheet, pivotTableSheetPath, newPivotTableColFieldsError(colDataFields)
}
if len(rowDataFields) > 0 {
return dataSheet, pivotTableSheetPath, newPivotTableRowFieldsError(rowDataFields)
}
return dataSheet, pivotTableSheetPath, err
}
// adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
func (f *File) adjustRange(rangeStr string) (string, []int, error) {
if len(rangeStr) < 1 {
return "", []int{}, ErrParameterRequired
}
rng := strings.Split(rangeStr, "!")
if len(rng) != 2 {
return "", []int{}, ErrParameterInvalid
}
trimRng := strings.ReplaceAll(rng[1], "$", "")
coordinates, err := rangeRefToCoordinates(trimRng)
if err != nil {
return rng[0], []int{}, err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if x1 == x2 && y1 == y2 {
return rng[0], []int{}, ErrParameterInvalid
}
// Correct the range, such correct C1:B3 to B1:C3.
if x2 < x1 {
x1, x2 = x2, x1
}
if y2 < y1 {
y1, y2 = y2, y1
}
return rng[0], []int{x1, y1, x2, y2}, nil
}
// getTableFieldsOrder provides a function to get order list of pivot table
// fields.
func (f *File) getTableFieldsOrder(opts *PivotTableOptions) ([]string, error) {
var order []string
if err := f.getPivotTableDataRange(opts); err != nil {
return order, err
}
dataSheet, coordinates, err := f.adjustRange(opts.pivotDataRange)
if err != nil {
return order, newPivotTableDataRangeError(err.Error())
}
for col := coordinates[0]; col <= coordinates[2]; col++ {
coordinate, _ := CoordinatesToCellName(col, coordinates[1])
name, err := f.GetCellValue(dataSheet, coordinate)
if err != nil {
return order, err
}
if name == "" {
return order, ErrParameterInvalid
}
order = append(order, name)
}
return order, nil
}
// addPivotCache provides a function to create a pivot cache by given properties.
func (f *File) addPivotCache(opts *PivotTableOptions) error {
// validate data range
dataSheet, coordinates, err := f.adjustRange(opts.pivotDataRange)
if err != nil {
return newPivotTableDataRangeError(err.Error())
}
order, err := f.getTableFieldsOrder(opts)
if err != nil {
return newPivotTableDataRangeError(err.Error())
}
topLeftCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
bottomRightCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
pc := xlsxPivotCacheDefinition{
SaveData: false,
RefreshOnLoad: true,
CreatedVersion: pivotTableVersion,
RefreshedVersion: pivotTableRefreshedVersion,
MinRefreshableVersion: pivotTableVersion,
CacheSource: &xlsxCacheSource{
Type: "worksheet",
WorksheetSource: &xlsxWorksheetSource{
Ref: topLeftCell + ":" + bottomRightCell,
Sheet: dataSheet,
},
},
CacheFields: &xlsxCacheFields{},
}
if opts.namedDataRange {
pc.CacheSource.WorksheetSource = &xlsxWorksheetSource{Name: opts.DataRange}
}
for _, name := range order {
pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
Name: name,
SharedItems: &xlsxSharedItems{ContainsBlank: true, M: []xlsxMissing{{}}},
})
}
pc.CacheFields.Count = len(pc.CacheFields.CacheField)
pivotCache, err := xml.Marshal(pc)
f.saveFileList(opts.pivotCacheXML, pivotCache)
return err
}
// addPivotTable provides a function to create a pivot table by given pivot
// table ID and properties.
func (f *File) addPivotTable(cacheID, pivotTableID int, opts *PivotTableOptions) error {
// validate pivot table range
_, coordinates, err := f.adjustRange(opts.PivotTableRange)
if err != nil {
return newPivotTableRangeError(err.Error())
}
topLeftCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
bottomRightCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
pivotTableStyle := func() string {
if opts.PivotTableStyleName == "" {
return "PivotStyleLight16"
}
return opts.PivotTableStyleName
}
pt := xlsxPivotTableDefinition{
Name: opts.Name,
CacheID: cacheID,
RowGrandTotals: &opts.RowGrandTotals,
ColGrandTotals: &opts.ColGrandTotals,
UpdatedVersion: pivotTableRefreshedVersion,
MinRefreshableVersion: pivotTableVersion,
ShowDrill: &opts.ShowDrill,
UseAutoFormatting: &opts.UseAutoFormatting,
PageOverThenDown: &opts.PageOverThenDown,
MergeItem: &opts.MergeItem,
CreatedVersion: pivotTableVersion,
CompactData: &opts.CompactData,
GridDropZones: opts.ClassicLayout,
ShowError: &opts.ShowError,
FieldPrintTitles: opts.FieldPrintTitles,
ItemPrintTitles: opts.ItemPrintTitles,
DataCaption: "Values",
Location: &xlsxLocation{
Ref: topLeftCell + ":" + bottomRightCell,
FirstDataCol: 1,
FirstDataRow: 1,
FirstHeaderRow: 1,
},
PivotFields: &xlsxPivotFields{},
RowItems: &xlsxRowItems{
Count: 1,
I: []*xlsxI{
{
[]*xlsxX{{}},
},
},
},
ColItems: &xlsxColItems{
Count: 1,
I: []*xlsxI{{}},
},
PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
Name: pivotTableStyle(),
ShowRowHeaders: opts.ShowRowHeaders,
ShowColHeaders: opts.ShowColHeaders,
ShowRowStripes: opts.ShowRowStripes,
ShowColStripes: opts.ShowColStripes,
ShowLastColumn: opts.ShowLastColumn,
},
}
if pt.Name == "" {
pt.Name = fmt.Sprintf("PivotTable%d", pivotTableID)
}
// set classic layout
if opts.ClassicLayout {
pt.Compact, pt.CompactData = boolPtr(false), boolPtr(false)
}
// pivot fields
_ = f.addPivotFields(&pt, opts)
// count pivot fields
pt.PivotFields.Count = len(pt.PivotFields.PivotField)
// data range has been checked
_ = f.addPivotRowFields(&pt, opts)
_ = f.addPivotColFields(&pt, opts)
_ = f.addPivotPageFields(&pt, opts)
_ = f.addPivotDataFields(&pt, opts)
pivotTable, err := xml.Marshal(pt)
f.saveFileList(opts.pivotTableXML, pivotTable)
return err
}
// addPivotRowFields provides a method to add row fields for pivot table by
// given pivot table options.
func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
// row fields
rowFieldsIndex, err := f.getPivotFieldsIndex(opts.Rows, opts)
if err != nil {
return err
}
for _, fieldIdx := range rowFieldsIndex {
if pt.RowFields == nil {
pt.RowFields = &xlsxRowFields{}
}
pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{
X: fieldIdx,
})
}
// count row fields
if pt.RowFields != nil {
pt.RowFields.Count = len(pt.RowFields.Field)
}
return err
}
// addPivotPageFields provides a method to add page fields for pivot table by
// given pivot table options.
func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
// page fields
pageFieldsIndex, err := f.getPivotFieldsIndex(opts.Filter, opts)
if err != nil {
return err
}
pageFieldsName := f.getPivotTableFieldsName(opts.Filter)
for idx, pageField := range pageFieldsIndex {
if pt.PageFields == nil {
pt.PageFields = &xlsxPageFields{}
}
pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{
Name: pageFieldsName[idx],
Fld: pageField,
})
}
// count page fields
if pt.PageFields != nil {
pt.PageFields.Count = len(pt.PageFields.PageField)
}
return err
}
// addPivotDataFields provides a method to add data fields for pivot table by
// given pivot table options.
func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
// data fields
dataFieldsIndex, err := f.getPivotFieldsIndex(opts.Data, opts)
if err != nil {
return err
}
dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opts.Data)
dataFieldsName := f.getPivotTableFieldsName(opts.Data)
dataFieldsNumFmtID := f.getPivotTableFieldsNumFmtID(opts.Data)
for idx, dataField := range dataFieldsIndex {
if pt.DataFields == nil {
pt.DataFields = &xlsxDataFields{}
}
pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{
Name: dataFieldsName[idx],
Fld: dataField,
Subtotal: dataFieldsSubtotals[idx],
NumFmtID: dataFieldsNumFmtID[idx],
})
}
// count data fields
if pt.DataFields != nil {
pt.DataFields.Count = len(pt.DataFields.DataField)
}
return err
}
// inPivotTableField provides a method to check if an element is present in
// pivot table fields list, and return the index of its location, otherwise
// return -1.
func inPivotTableField(a []PivotTableField, x string) int {
for idx, n := range a {
if x == n.Data {
return idx
}
}
return -1
}
// addPivotColFields create pivot column fields by given pivot table
// definition and option.
func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
if len(opts.Columns) == 0 {
if len(opts.Data) <= 1 {
return nil
}
pt.ColFields = &xlsxColFields{}
// in order to create pivot table in case there is no input from Columns
pt.ColFields.Count = 1
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
X: -2,
})
return nil
}
pt.ColFields = &xlsxColFields{}
// col fields
colFieldsIndex, err := f.getPivotFieldsIndex(opts.Columns, opts)
if err != nil {
return err
}
for _, fieldIdx := range colFieldsIndex {
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
X: fieldIdx,
})
}
// in order to create pivot in case there is many Columns and Data
if len(opts.Data) > 1 {
pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
X: -2,
})
}
// count col fields
pt.ColFields.Count = len(pt.ColFields.Field)
return err
}
// setClassicLayout provides a method to set classic layout for pivot table by
// setting Compact and Outline to false.
func (fld *xlsxPivotField) setClassicLayout(classicLayout bool) {
if classicLayout {
fld.Compact, fld.Outline = boolPtr(false), boolPtr(false)
}
}
// addPivotFields create pivot fields based on the column order of the first
// row in the data region by given pivot table definition and option.
func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
order, err := f.getTableFieldsOrder(opts)
if err != nil {
return err
}
x := 0
for _, name := range order {
if inPivotTableField(opts.Rows, name) != -1 {
rowOptions, ok := f.getPivotTableFieldOptions(name, opts.Rows)
var items []*xlsxItem
if !ok || !rowOptions.DefaultSubtotal {
items = append(items, &xlsxItem{X: &x})
} else {
items = append(items, &xlsxItem{T: "default"})
}
fld := &xlsxPivotField{
Name: f.getPivotTableFieldName(name, opts.Rows),
Axis: "axisRow",
DataField: inPivotTableField(opts.Data, name) != -1,
Compact: &rowOptions.Compact,
Outline: &rowOptions.Outline,
ShowAll: rowOptions.ShowAll,
InsertBlankRow: rowOptions.InsertBlankRow,
DefaultSubtotal: &rowOptions.DefaultSubtotal,
Items: &xlsxItems{
Count: len(items),
Item: items,
},
}
fld.setClassicLayout(opts.ClassicLayout)
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
continue
}
if inPivotTableField(opts.Filter, name) != -1 {
fld := &xlsxPivotField{
Axis: "axisPage",
DataField: inPivotTableField(opts.Data, name) != -1,
Name: f.getPivotTableFieldName(name, opts.Columns),
Items: &xlsxItems{
Count: 1,
Item: []*xlsxItem{
{T: "default"},
},
},
}
fld.setClassicLayout(opts.ClassicLayout)
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
continue
}
if inPivotTableField(opts.Columns, name) != -1 {
columnOptions, ok := f.getPivotTableFieldOptions(name, opts.Columns)
var items []*xlsxItem
if !ok || !columnOptions.DefaultSubtotal {
items = append(items, &xlsxItem{X: &x})
} else {
items = append(items, &xlsxItem{T: "default"})
}
fld := &xlsxPivotField{
Name: f.getPivotTableFieldName(name, opts.Columns),
Axis: "axisCol",
DataField: inPivotTableField(opts.Data, name) != -1,
Compact: &columnOptions.Compact,
Outline: &columnOptions.Outline,
ShowAll: columnOptions.ShowAll,
InsertBlankRow: columnOptions.InsertBlankRow,
DefaultSubtotal: &columnOptions.DefaultSubtotal,
Items: &xlsxItems{
Count: len(items),
Item: items,
},
}
fld.setClassicLayout(opts.ClassicLayout)
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
continue
}
if inPivotTableField(opts.Data, name) != -1 {
fld := &xlsxPivotField{
DataField: true,
}
fld.setClassicLayout(opts.ClassicLayout)
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
continue
}
fld := &xlsxPivotField{}
fld.setClassicLayout(opts.ClassicLayout)
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
}
return err
}
// countPivotTables provides a function to get pivot table files count storage
// in the folder xl/pivotTables.
func (f *File) countPivotTables() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/pivotTables/pivotTable") {
count++
}
return true
})
return count
}
// countPivotCache provides a function to get pivot table cache definition files
// count storage in the folder xl/pivotCache.
func (f *File) countPivotCache() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") {
count++
}
return true
})
return count
}
// getPivotFieldsIndex convert the column of the first row in the data region
// to a sequential index by given fields and pivot option.
func (f *File) getPivotFieldsIndex(fields []PivotTableField, opts *PivotTableOptions) ([]int, error) {
var pivotFieldsIndex []int
orders, err := f.getTableFieldsOrder(opts)
if err != nil {
return pivotFieldsIndex, err
}
for _, field := range fields {
if pos := inStrSlice(orders, field.Data, true); pos != -1 {
pivotFieldsIndex = append(pivotFieldsIndex, pos)
}
}
return pivotFieldsIndex, nil
}
// getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields.
func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string {
field := make([]string, len(fields))
enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"}
inEnums := func(enums []string, val string) string {
for _, enum := range enums {
if strings.EqualFold(enum, val) {
return enum
}
}
return "sum"
}
for idx, fld := range fields {
field[idx] = inEnums(enums, fld.Subtotal)
}
return field
}
// getPivotTableFieldsName prepare fields name list by given pivot table
// fields.
func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string {
field := make([]string, len(fields))
for idx, fld := range fields {
if countUTF16String(fld.Name) > MaxFieldLength {
field[idx] = truncateUTF16Units(fld.Name, MaxFieldLength)
continue
}
field[idx] = fld.Name
}
return field
}
// getPivotTableFieldName prepare field name by given pivot table fields.
func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string {
fieldsName := f.getPivotTableFieldsName(fields)
for idx, field := range fields {
if field.Data == name {
return fieldsName[idx]
}
}
return ""
}
// getPivotTableFieldsNumFmtID prepare fields number format ID by given pivot
// table fields.
func (f *File) getPivotTableFieldsNumFmtID(fields []PivotTableField) []int {
field := make([]int, len(fields))
for idx, fld := range fields {
if _, ok := builtInNumFmt[fld.NumFmt]; ok {
field[idx] = fld.NumFmt
continue
}
if (27 <= fld.NumFmt && fld.NumFmt <= 36) || (50 <= fld.NumFmt && fld.NumFmt <= 81) {
field[idx] = fld.NumFmt
}
}
return field
}
// getPivotTableFieldOptions return options for specific field by given field name.
func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) {
for _, field := range fields {
if field.Data == name {
options, ok = field, true
return
}
}
return
}
// addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml.
func (f *File) addWorkbookPivotCache(RID int) int {
wb, _ := f.workbookReader()
if wb.PivotCaches == nil {
wb.PivotCaches = &xlsxPivotCaches{}
}
cacheID := 1
for _, pivotCache := range wb.PivotCaches.PivotCache {
if pivotCache.CacheID > cacheID {
cacheID = pivotCache.CacheID
}
}
cacheID++
wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{
CacheID: cacheID,
RID: fmt.Sprintf("rId%d", RID),
})
return cacheID
}
// GetPivotTables returns all pivot table definitions in a worksheet by given
// worksheet name. Currently only support get pivot table cache with worksheet
// source type, and doesn't support source types: external, consolidation
// and scenario.
func (f *File) GetPivotTables(sheet string) ([]PivotTableOptions, error) {
var pivotTables []PivotTableOptions
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return pivotTables, ErrSheetNotExist{sheet}
}
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels, err := f.relsReader(rels)
if err != nil {
return pivotTables, err
}
if sheetRels == nil {
sheetRels = &xlsxRelationships{}
}
for _, v := range sheetRels.Relationships {
if v.Type == SourceRelationshipPivotTable {
pivotTableXML := strings.ReplaceAll(v.Target, "..", "xl")
pivotCacheRels := "xl/pivotTables/_rels/" + filepath.Base(v.Target) + ".rels"
pivotTable, err := f.getPivotTable(sheet, pivotTableXML, pivotCacheRels)
if err != nil {
return pivotTables, err
}
pivotTables = append(pivotTables, pivotTable)
}
}
return pivotTables, nil
}
// getPivotTableDataRange checking given if data range is a cell reference or
// named reference (defined name or table name), and set pivot table data range.
func (f *File) getPivotTableDataRange(opts *PivotTableOptions) error {
if opts.DataRange == "" {
return newPivotTableDataRangeError(ErrParameterRequired.Error())
}
if opts.pivotDataRange != "" {
return nil
}
if strings.Contains(opts.DataRange, "!") {
opts.pivotDataRange = opts.DataRange
return nil
}
tbls, err := f.getTables()
if err != nil {
return err
}
for sheetName, tables := range tbls {
for _, table := range tables {
if table.Name == opts.DataRange {
opts.pivotDataRange, opts.namedDataRange = fmt.Sprintf("%s!%s", sheetName, table.Range), true
return err
}
}
}
if !opts.namedDataRange {
opts.pivotDataRange = f.getDefinedNameRefTo(opts.DataRange, opts.pivotSheetName)
if opts.pivotDataRange != "" {
opts.namedDataRange = true
return nil
}
}
return newPivotTableDataRangeError(ErrParameterInvalid.Error())
}
// getPivotTable provides a function to get a pivot table definition by given
// worksheet name, pivot table XML path and pivot cache relationship XML path.
func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (PivotTableOptions, error) {
var opts PivotTableOptions
rels, err := f.relsReader(pivotCacheRels)
if err != nil {
return opts, err
}
var pivotCacheXML string
for _, v := range rels.Relationships {
if v.Type == SourceRelationshipPivotCache {
pivotCacheXML = strings.ReplaceAll(v.Target, "..", "xl")
break
}
}
pc, err := f.pivotCacheReader(pivotCacheXML)
if err != nil {
return opts, err
}
pt, err := f.pivotTableReader(pivotTableXML)
if err != nil {
return opts, err
}
if pc.CacheSource.WorksheetSource == nil {
return opts, newUnsupportedPivotCacheSourceType(pc.CacheSource.Type)
}
opts = PivotTableOptions{
pivotTableXML: pivotTableXML,
pivotCacheXML: pivotCacheXML,
pivotSheetName: sheet,
DataRange: fmt.Sprintf("%s!%s", pc.CacheSource.WorksheetSource.Sheet, pc.CacheSource.WorksheetSource.Ref),
PivotTableRange: fmt.Sprintf("%s!%s", sheet, pt.Location.Ref),
Name: pt.Name,
ClassicLayout: pt.GridDropZones,
FieldPrintTitles: pt.FieldPrintTitles,
ItemPrintTitles: pt.ItemPrintTitles,
}
if pc.CacheSource.WorksheetSource.Name != "" {
opts.DataRange = pc.CacheSource.WorksheetSource.Name
_ = f.getPivotTableDataRange(&opts)
}
setPtrFieldsVal([]string{
"RowGrandTotals", "ColGrandTotals", "ShowDrill",
"UseAutoFormatting", "PageOverThenDown", "MergeItem", "CompactData", "ShowError",
}, reflect.ValueOf(*pt), reflect.ValueOf(&opts).Elem())
if si := pt.PivotTableStyleInfo; si != nil {
opts.ShowRowHeaders = si.ShowRowHeaders
opts.ShowColHeaders = si.ShowColHeaders
opts.ShowRowStripes = si.ShowRowStripes
opts.ShowColStripes = si.ShowColStripes
opts.ShowLastColumn = si.ShowLastColumn
opts.PivotTableStyleName = si.Name
}
if err = f.getPivotTableDataRange(&opts); err != nil {
return opts, err
}
f.extractPivotTableFields(pc.getPivotCacheFieldsName(), pt, &opts)
return opts, err
}
// getPivotCacheFieldsName returns pivot table fields name list by order from
// pivot cache fields.
func (pc *xlsxPivotCacheDefinition) getPivotCacheFieldsName() []string {
var order []string
if pc.CacheFields != nil {
for _, cf := range pc.CacheFields.CacheField {
if cf != nil {
order = append(order, cf.Name)
}
}
}
return order
}
// pivotTableReader provides a function to get the pointer to the structure
// after deserialization of xl/pivotTables/pivotTable%d.xml.
func (f *File) pivotTableReader(path string) (*xlsxPivotTableDefinition, error) {
content, ok := f.Pkg.Load(path)
pivotTable := &xlsxPivotTableDefinition{}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(pivotTable); err != nil && err != io.EOF {
return nil, err
}
}
return pivotTable, nil
}
// pivotCacheReader provides a function to get the pointer to the structure
// after deserialization of xl/pivotCache/pivotCacheDefinition%d.xml.
func (f *File) pivotCacheReader(path string) (*xlsxPivotCacheDefinition, error) {
content, ok := f.Pkg.Load(path)
pivotCache := &xlsxPivotCacheDefinition{}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(pivotCache); err != nil && err != io.EOF {
return nil, err
}
}
return pivotCache, nil
}
// extractPivotTableFields provides a function to extract all pivot table fields
// settings by given pivot table fields.
func (f *File) extractPivotTableFields(order []string, pt *xlsxPivotTableDefinition, opts *PivotTableOptions) {
for fieldIdx, field := range pt.PivotFields.PivotField {
if field.Axis == "axisRow" {
opts.Rows = append(opts.Rows, extractPivotTableField(order[fieldIdx], field))
}
if field.Axis == "axisCol" {
opts.Columns = append(opts.Columns, extractPivotTableField(order[fieldIdx], field))
}
if field.Axis == "axisPage" {
opts.Filter = append(opts.Filter, extractPivotTableField(order[fieldIdx], field))
}
}
if pt.DataFields != nil {
for _, field := range pt.DataFields.DataField {
opts.Data = append(opts.Data, PivotTableField{
Data: order[field.Fld],
Name: field.Name,
Subtotal: cases.Title(language.English).String(field.Subtotal),
NumFmt: field.NumFmtID,
})
}
}
}
// extractPivotTableField provides a function to extract pivot table field
// settings by given pivot table fields.
func extractPivotTableField(data string, fld *xlsxPivotField) PivotTableField {
pivotTableField := PivotTableField{
Data: data,
ShowAll: fld.ShowAll,
InsertBlankRow: fld.InsertBlankRow,
}
setPtrFieldsVal([]string{"Compact", "Outline", "DefaultSubtotal"},
reflect.ValueOf(*fld), reflect.ValueOf(&pivotTableField).Elem())
return pivotTableField
}
// genPivotCacheDefinitionID generates a unique pivot table cache definition ID.
func (f *File) genPivotCacheDefinitionID() int {
var (
ID int
decodeExtLst = new(decodeExtLst)
decodeX14PivotCacheDefinition = new(decodeX14PivotCacheDefinition)
)
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") {
pc, err := f.pivotCacheReader(k.(string))
if err != nil {
return true
}
if pc.ExtLst != nil {
_ = f.xmlNewDecoder(strings.NewReader("" + pc.ExtLst.Ext + "")).Decode(decodeExtLst)
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURIPivotCacheDefinition {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeX14PivotCacheDefinition)
if ID < decodeX14PivotCacheDefinition.PivotCacheID {
ID = decodeX14PivotCacheDefinition.PivotCacheID
}
}
}
}
}
return true
})
return ID + 1
}
// deleteWorkbookPivotCache remove workbook pivot cache and pivot cache
// relationships.
func (f *File) deleteWorkbookPivotCache(opt PivotTableOptions) error {
rID, err := f.deleteWorkbookRels(SourceRelationshipPivotCache, strings.TrimPrefix(strings.TrimPrefix(opt.pivotCacheXML, "/"), "xl/"))
if err != nil {
return err
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.PivotCaches != nil {
for i, pivotCache := range wb.PivotCaches.PivotCache {
if pivotCache.RID == rID {
wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache[:i], wb.PivotCaches.PivotCache[i+1:]...)
}
}
if len(wb.PivotCaches.PivotCache) == 0 {
wb.PivotCaches = nil
}
}
return err
}
// DeletePivotTable delete a pivot table by giving the worksheet name and pivot
// table name. Note that this function does not clean cell values in the pivot
// table range.
func (f *File) DeletePivotTable(sheet, name string) error {
sheetXML, ok := f.getSheetXMLPath(sheet)
if !ok {
return ErrSheetNotExist{sheet}
}
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXML, "xl/worksheets/") + ".rels"
sheetRels, err := f.relsReader(rels)
if err != nil {
return err
}
if sheetRels == nil {
sheetRels = &xlsxRelationships{}
}
opts, err := f.GetPivotTables(sheet)
if err != nil {
return err
}
f.clearCalcCache()
pivotTableCaches := map[string]int{}
pivotTables, _ := f.getPivotTables()
for _, sheetPivotTables := range pivotTables {
for _, sheetPivotTable := range sheetPivotTables {
pivotTableCaches[sheetPivotTable.pivotCacheXML]++
}
}
for _, v := range sheetRels.Relationships {
for _, opt := range opts {
if v.Type == SourceRelationshipPivotTable {
pivotTableXML := strings.ReplaceAll(v.Target, "..", "xl")
if opt.Name == name && opt.pivotTableXML == pivotTableXML {
if pivotTableCaches[opt.pivotCacheXML] == 1 {
err = f.deleteWorkbookPivotCache(opt)
}
f.deleteSheetRelationships(sheet, v.ID)
return err
}
}
}
}
return newNoExistTableError(name)
}
// getPivotTables provides a function to get all pivot tables in a workbook.
func (f *File) getPivotTables() (map[string][]PivotTableOptions, error) {
pivotTables := map[string][]PivotTableOptions{}
for _, sheetName := range f.GetSheetList() {
pts, err := f.GetPivotTables(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return pivotTables, err
}
pivotTables[sheetName] = append(pivotTables[sheetName], pts...)
}
return pivotTables, nil
}
================================================
FILE: pivotTable_test.go
================================================
package excelize
import (
"fmt"
"math/rand"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPivotTable(t *testing.T) {
f := NewFile()
// Create some data in a sheet
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
year := []int{2017, 2018, 2019}
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
region := []string{"East", "West", "North", "South"}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
for row := 2; row < 32; row++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
}
expected := &PivotTableOptions{
pivotTableXML: "xl/pivotTables/pivotTable1.xml",
pivotCacheXML: "xl/pivotCache/pivotCacheDefinition1.xml",
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Name: "PivotTable1",
Rows: []PivotTableField{{Data: "Month", ShowAll: true, DefaultSubtotal: true}, {Data: "Year"}},
Filter: []PivotTableField{{Data: "Region"}},
Columns: []PivotTableField{{Data: "Type", ShowAll: true, InsertBlankRow: true, DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum", NumFmt: 38}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ClassicLayout: true,
ShowError: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
FieldPrintTitles: true,
ItemPrintTitles: true,
PivotTableStyleName: "PivotStyleLight16",
}
assert.NoError(t, f.AddPivotTable(expected))
// Test get pivot table
pivotTables, err := f.GetPivotTables("Sheet1")
assert.NoError(t, err)
assert.Len(t, pivotTables, 1)
assert.Equal(t, *expected, pivotTables[0])
// Use different order of coordinate tests
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Test get pivot table with default style name
pivotTables, err = f.GetPivotTables("Sheet1")
assert.NoError(t, err)
assert.Len(t, pivotTables, 2)
assert.Equal(t, "PivotStyleLight16", pivotTables[1].PivotTableStyleName)
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!W2:AC34",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Region"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Count", Name: "Summarize by Count"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G42:W55",
Rows: []PivotTableField{{Data: "Month"}},
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "CountNums", Name: "Summarize by CountNums"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!AE2:AG33",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Create pivot table with empty subtotal field name and specified style
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!AJ2:AP135",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Filter: []PivotTableField{{Data: "Region"}},
Columns: []PivotTableField{},
Data: []PivotTableField{{Subtotal: "Sum", Name: "Summarize by Sum"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
PivotTableStyleName: "PivotStyleLight19",
}))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet2!A1:AN17",
Rows: []PivotTableField{{Data: "Month"}},
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type", DefaultSubtotal: true}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min", NumFmt: 32}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Test get pivot table with across worksheet data range
pivotTables, err = f.GetPivotTables("Sheet2")
assert.NoError(t, err)
assert.Len(t, pivotTables, 1)
assert.Equal(t, "Sheet1!A1:E31", pivotTables[0].DataRange)
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet2!A20:AR60",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Type"}},
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product", NumFmt: 32}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Create pivot table with many data, many rows, many cols and defined name
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "dataRange",
RefersTo: "Sheet1!A1:E31",
Comment: "Pivot Table Data Range",
Scope: "Sheet2",
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "dataRange",
PivotTableRange: "Sheet2!A65:AJ100",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type"}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Sum of Sales", NumFmt: -1}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales", NumFmt: 38}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Test empty pivot table options
assert.Equal(t, ErrParameterRequired, f.AddPivotTable(nil))
// Test add pivot table with custom name which exceeds the max characters limit
assert.Equal(t, ErrNameLength, f.AddPivotTable(&PivotTableOptions{
DataRange: "dataRange",
PivotTableRange: "Sheet2!A65:AJ100",
Name: strings.Repeat("c", MaxFieldLength+1),
}))
// Test invalid data range
assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:A1",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test the data range of the worksheet that is not declared
assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{
DataRange: "A1:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test the worksheet declared in the data range does not exist
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{
DataRange: "SheetN!A1:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test the pivot table range of the worksheet that is not declared
assert.Equal(t, newPivotTableRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test the worksheet declared in the pivot table range does not exist
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "SheetN!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test not exists worksheet in data range
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddPivotTable(&PivotTableOptions{
DataRange: "SheetN!A1:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
// Test invalid row number in data range
assert.Equal(t, newPivotTableDataRangeError(newCellNameToCoordinatesError("A0", newInvalidCellNameError("A0")).Error()), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A0:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable1.xlsx")))
// Test with field names that exceed the length limit and invalid subtotal
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", MaxFieldLength+1)}},
}))
// Test with same data field appears both in the pivot table column fields and filter fields
assert.Equal(t, newPivotTableColFieldsError([]string{"Type"}), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
Filter: []PivotTableField{{Data: "Type"}},
}))
// Test with same data field appears both in the pivot table row fields and filter fields
assert.Equal(t, newPivotTableRowFieldsError([]string{"Month"}), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
Filter: []PivotTableField{{Data: "Month"}},
}))
// Test delete pivot table
pivotTables, err = f.GetPivotTables("Sheet1")
assert.Len(t, pivotTables, 7)
assert.NoError(t, err)
assert.NoError(t, f.DeletePivotTable("Sheet1", "PivotTable1"))
pivotTables, err = f.GetPivotTables("Sheet1")
assert.Len(t, pivotTables, 6)
assert.NoError(t, err)
// Test add pivot table with invalid sheet name
assert.Error(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet:1!A1:E31",
PivotTableRange: "Sheet:1!G2:M34",
Rows: []PivotTableField{{Data: "Year"}},
}), ErrSheetNameInvalid)
// Test add pivot table with enable ClassicLayout and CompactData in the same time
assert.Error(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
CompactData: true,
ClassicLayout: true,
}), ErrPivotTableClassicLayout)
// Test delete pivot table with not exists worksheet
assert.EqualError(t, f.DeletePivotTable("SheetN", "PivotTable1"), "sheet SheetN does not exist")
// Test delete pivot table with not exists pivot table name
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTableN"), "table PivotTableN does not exist")
// Test adjust range with invalid range
_, _, err = f.adjustRange("")
assert.Error(t, err, ErrParameterRequired)
// Test adjust range with incorrect range
_, _, err = f.adjustRange("sheet1!")
assert.EqualError(t, err, "parameter is invalid")
// Test get table fields order with empty data range
_, err = f.getTableFieldsOrder(&PivotTableOptions{})
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
// Test add pivot cache with empty data range
assert.EqualError(t, f.addPivotCache(&PivotTableOptions{}), "parameter 'DataRange' parsing error: parameter is required")
// Test add pivot table with empty options
assert.EqualError(t, f.addPivotTable(0, 0, &PivotTableOptions{}), "parameter 'PivotTableRange' parsing error: parameter is required")
// Test add pivot table with invalid data range
assert.EqualError(t, f.addPivotTable(0, 0, &PivotTableOptions{}), "parameter 'PivotTableRange' parsing error: parameter is required")
// Test add pivot fields with empty data range
assert.EqualError(t, f.addPivotFields(nil, &PivotTableOptions{
DataRange: "A1:E31",
PivotTableRange: "Sheet1!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales"}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
// Test get pivot fields index with empty data range
_, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOptions{})
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
// Test add pivot table with unsupported charset content types.
f = NewFile()
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Rows: []PivotTableField{{Data: "Year"}},
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get pivot table without pivot table
f = NewFile()
pivotTables, err = f.GetPivotTables("Sheet1")
assert.NoError(t, err)
assert.Len(t, pivotTables, 0)
// Test get pivot table with not exists worksheet
_, err = f.GetPivotTables("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get pivot table with unsupported charset worksheet relationships
f.Pkg.Store("xl/worksheets/_rels/sheet1.xml.rels", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get pivot table with unsupported charset pivot cache definition
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
assert.NoError(t, err)
f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get pivot table with unsupported charset pivot table relationships
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
assert.NoError(t, err)
f.Pkg.Store("xl/pivotTables/_rels/pivotTable1.xml.rels", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get pivot table with unsupported charset pivot table
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
assert.NoError(t, err)
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
_, err = f.getPivotTables()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test get pivot table with unsupported pivot table cache source type
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
assert.NoError(t, err)
f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value))
_, err = f.GetPivotTables("Sheet1")
assert.Equal(t, err, newUnsupportedPivotCacheSourceType("external"))
assert.NoError(t, f.Close())
}
func TestPivotTableDataRange(t *testing.T) {
f := NewFile()
// Create table in a worksheet
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
for row := 2; row < 6; row++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), rand.Intn(10)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), rand.Intn(10)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), rand.Intn(10)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(10)))
}
// Test add pivot table with table data range
opts := PivotTableOptions{
DataRange: "Table1",
PivotTableRange: "Sheet1!G2:K7",
Rows: []PivotTableField{{Data: "Column1"}},
Columns: []PivotTableField{{Data: "Column2"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
ShowError: true,
PivotTableStyleName: "PivotStyleLight16",
}
assert.NoError(t, f.AddPivotTable(&opts))
assert.NoError(t, f.DeletePivotTable("Sheet1", "PivotTable1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable2.xlsx")))
assert.NoError(t, f.Close())
assert.NoError(t, f.AddPivotTable(&opts))
// Test delete pivot table with unsupported table relationships charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "XML syntax error on line 1: invalid UTF-8")
// Test delete pivot table with unsupported worksheet relationships charset
f.Relationships.Delete("xl/worksheets/_rels/sheet1.xml.rels")
f.Pkg.Store("xl/worksheets/_rels/sheet1.xml.rels", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "XML syntax error on line 1: invalid UTF-8")
// Test delete pivot table without worksheet relationships
f.Relationships.Delete("xl/worksheets/_rels/sheet1.xml.rels")
f.Pkg.Delete("xl/worksheets/_rels/sheet1.xml.rels")
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "table PivotTable1 does not exist")
t.Run("data_range_with_empty_column", func(t *testing.T) {
// Test add pivot table with data range doesn't organized as a list with labeled columns
f := NewFile()
// Create some data in a sheet
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "", "Type"}))
for row := 2; row < 32; row++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
}
assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!A1:E31",
PivotTableRange: "Sheet1!G2:M34",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Type"}},
}))
})
}
func TestParseFormatPivotTableSet(t *testing.T) {
f := NewFile()
// Create table in a worksheet
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
// Test parse format pivot table options with unsupported table relationships charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, _, err := f.parseFormatPivotTableSet(&PivotTableOptions{
DataRange: "Table1",
PivotTableRange: "Sheet1!G2:K7",
Rows: []PivotTableField{{Data: "Column1"}},
})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestAddPivotRowFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotRowFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
DataRange: "Sheet1!A1:A1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotPageFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotPageFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
DataRange: "Sheet1!A1:A1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotDataFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotDataFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
DataRange: "Sheet1!A1:A1",
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestAddPivotColFields(t *testing.T) {
f := NewFile()
// Test invalid data range
assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
DataRange: "Sheet1!A1:A1",
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
}), `parameter 'DataRange' parsing error: parameter is invalid`)
}
func TestGetPivotFieldsOrder(t *testing.T) {
f := NewFile()
// Test get table fields order with not exist worksheet
_, err := f.getTableFieldsOrder(&PivotTableOptions{DataRange: "SheetN!A1:E31"})
assert.EqualError(t, err, "sheet SheetN does not exist")
// Create table in a worksheet
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
// Test get table fields order with unsupported table relationships charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, err = f.getTableFieldsOrder(&PivotTableOptions{DataRange: "Table"})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetPivotTableFieldName(t *testing.T) {
f := NewFile()
assert.Empty(t, f.getPivotTableFieldName("-", []PivotTableField{}))
}
func TestGetPivotTableFieldOptions(t *testing.T) {
f := NewFile()
_, ok := f.getPivotTableFieldOptions("-", []PivotTableField{})
assert.False(t, ok)
}
func TestGenPivotCacheDefinitionID(t *testing.T) {
f := NewFile()
// Test generate pivot table cache definition ID with unsupported charset
f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", MacintoshCyrillicCharset)
assert.Equal(t, 1, f.genPivotCacheDefinitionID())
assert.NoError(t, f.Close())
}
func TestDeleteWorkbookPivotCache(t *testing.T) {
f := NewFile()
// Test delete workbook pivot table cache with unsupported workbook charset
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteWorkbookPivotCache(PivotTableOptions{pivotCacheXML: "pivotCache/pivotCacheDefinition1.xml"}), "XML syntax error on line 1: invalid UTF-8")
// Test delete workbook pivot table cache with unsupported workbook relationships charset
f.Relationships.Delete("xl/_rels/workbook.xml.rels")
f.Pkg.Store("xl/_rels/workbook.xml.rels", MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteWorkbookPivotCache(PivotTableOptions{pivotCacheXML: "pivotCache/pivotCacheDefinition1.xml"}), "XML syntax error on line 1: invalid UTF-8")
}
================================================
FILE: rows.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
"math"
"os"
"strconv"
"strings"
"github.com/tiendc/go-deepcopy"
)
// duplicateHelperFunc defines functions to duplicate helper.
var duplicateHelperFunc = [3]func(*File, *xlsxWorksheet, string, int, int) error{
func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error {
return f.duplicateConditionalFormat(ws, sheet, row, row2)
},
func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error {
return f.duplicateDataValidations(ws, sheet, row, row2)
},
func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error {
return f.duplicateMergeCells(ws, sheet, row, row2)
},
}
// GetRows return all the rows in a sheet by given worksheet name, returned as
// a two-dimensional array, where the value of the cell is converted to the
// string type. If the cell format can be applied to the value of the cell,
// the applied value will be used, otherwise the original value will be used.
// GetRows fetched the rows with value or formula cells, the continually blank
// cells in the tail of each row will be skipped, so the length of each row
// may be inconsistent.
//
// For example, get and traverse the value of all cells by rows on a worksheet
// named 'Sheet1':
//
// rows, err := f.GetRows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for _, row := range rows {
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
rows, err := f.Rows(sheet)
if err != nil {
return nil, err
}
results, cur, maxVal := make([][]string, 0, 64), 0, 0
for rows.Next() {
cur++
row, err := rows.Columns(opts...)
if err != nil {
break
}
if len(row) > 0 {
if emptyRows := cur - maxVal - 1; emptyRows > 0 {
results = append(results, make([][]string, emptyRows)...)
}
results = append(results, row)
maxVal = cur
}
}
return results[:maxVal], rows.Close()
}
// Rows defines an iterator to a sheet.
type Rows struct {
err error
curRow, seekRow int
needClose, rawCellValue bool
sheet string
f *File
tempFile *os.File
sst *xlsxSST
decoder *xml.Decoder
token xml.Token
curRowOpts, seekRowOpts RowOpts
}
// Next will return true if it finds the next row element.
func (rows *Rows) Next() bool {
rows.seekRow++
if rows.curRow >= rows.seekRow {
rows.curRowOpts = rows.seekRowOpts
return true
}
for {
token, _ := rows.decoder.Token()
if token == nil {
return false
}
switch xmlElement := token.(type) {
case xml.StartElement:
if xmlElement.Name.Local == "row" {
rows.curRow++
if rowNum, _ := attrValToInt("r", xmlElement.Attr); rowNum != 0 {
rows.curRow = rowNum
}
rows.token = token
rows.curRowOpts = extractRowOpts(xmlElement.Attr)
return true
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
return false
}
}
}
}
// GetRowOpts will return the RowOpts of the current row.
func (rows *Rows) GetRowOpts() RowOpts {
return rows.curRowOpts
}
// Error will return the error when the error occurs.
func (rows *Rows) Error() error {
return rows.err
}
// Close closes the open worksheet XML file in the system temporary
// directory.
func (rows *Rows) Close() error {
tempFile := rows.tempFile
rows.tempFile = nil
if tempFile != nil {
return tempFile.Close()
}
return nil
}
// Columns return the current row's column values. This fetches the worksheet
// data as a stream, returns each cell in a row as is, and will not skip empty
// rows in the tail of the worksheet.
func (rows *Rows) Columns(opts ...Options) ([]string, error) {
if rows.curRow > rows.seekRow {
return nil, nil
}
var rowIterator rowXMLIterator
var token xml.Token
rows.rawCellValue = rows.f.getOptions(opts...).RawCellValue
if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
for {
if rows.token != nil {
token = rows.token
} else if token, _ = rows.decoder.Token(); token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
rowIterator.inElement = xmlElement.Name.Local
if rowIterator.inElement == "row" {
rowNum := 0
if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 {
rows.curRow = rowNum
} else if rows.token == nil {
rows.curRow++
}
rows.token = token
rows.seekRowOpts = extractRowOpts(xmlElement.Attr)
if rows.curRow > rows.seekRow {
rows.token = nil
return rowIterator.cells, rowIterator.err
}
}
if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil {
rows.token = nil
return rowIterator.cells, rowIterator.err
}
rows.token = nil
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
return rowIterator.cells, rowIterator.err
}
}
}
return rowIterator.cells, rowIterator.err
}
// extractRowOpts extract row element attributes.
func extractRowOpts(attrs []xml.Attr) RowOpts {
rowOpts := RowOpts{Height: defaultRowHeight}
if styleID, err := attrValToInt("s", attrs); err == nil && styleID > 0 && styleID < MaxCellStyles {
rowOpts.StyleID = styleID
}
if hidden, err := attrValToBool("hidden", attrs); err == nil {
rowOpts.Hidden = hidden
}
if height, err := attrValToFloat("ht", attrs); err == nil {
rowOpts.Height = height
}
return rowOpts
}
// appendSpace append blank characters to slice by given length and source slice.
func appendSpace(l int, s []string) []string {
for i := 1; i < l; i++ {
s = append(s, "")
}
return s
}
// rowXMLIterator defined runtime use field for the worksheet row SAX parser.
type rowXMLIterator struct {
err error
inElement string
cellCol, cellRow int
cells []string
}
// rowXMLHandler parse the row XML element of the worksheet.
func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, raw bool) {
if rowIterator.inElement == "c" {
rowIterator.cellCol++
colCell := xlsxC{}
_ = colCell.cellXMLHandler(rows.decoder, xmlElement)
if colCell.R != "" {
if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil {
return
}
}
blank := rowIterator.cellCol - len(rowIterator.cells)
if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil {
rowIterator.cells = append(appendSpace(blank, rowIterator.cells), val)
}
}
}
// cellXMLAttrHandler parse the cell XML element attributes of the worksheet.
func (cell *xlsxC) cellXMLAttrHandler(start *xml.StartElement) error {
for _, attr := range start.Attr {
switch attr.Name.Local {
case "r":
cell.R = attr.Value
case "s":
val, err := strconv.ParseInt(attr.Value, 10, 64)
if err != nil {
return err
}
if math.MinInt <= val && val <= math.MaxInt {
cell.S = int(val)
}
case "t":
cell.T = attr.Value
default:
}
}
return nil
}
// cellXMLHandler parse the cell XML element of the worksheet.
func (cell *xlsxC) cellXMLHandler(decoder *xml.Decoder, start *xml.StartElement) error {
cell.XMLName = start.Name
err := cell.cellXMLAttrHandler(start)
if err != nil {
return err
}
for {
tok, err := decoder.Token()
if err != nil {
return err
}
var se xml.StartElement
switch el := tok.(type) {
case xml.StartElement:
se = el
switch se.Name.Local {
case "v":
err = decoder.DecodeElement(&cell.V, &se)
case "f":
err = decoder.DecodeElement(&cell.F, &se)
case "is":
err = decoder.DecodeElement(&cell.IS, &se)
}
if err != nil {
return err
}
case xml.EndElement:
if el == start.End() {
return nil
}
}
}
}
// Rows returns a rows iterator, used for streaming reading data for a
// worksheet with a large data. This function is concurrency safe. For
// example:
//
// rows, err := f.Rows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// fmt.Println(err)
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
// if err = rows.Close(); err != nil {
// fmt.Println(err)
// }
func (f *File) Rows(sheet string) (*Rows, error) {
if err := checkSheetName(sheet); err != nil {
return nil, err
}
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
ws := worksheet.(*xlsxWorksheet)
ws.mu.Lock()
defer ws.mu.Unlock()
// Flush data
output, _ := xml.Marshal(ws)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var err error
rows := Rows{f: f, sheet: name}
rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name)
return &rows, err
}
// getFromStringItem build shared string item offset list from system temporary
// file at one time, and return value by given to string index.
func (f *File) getFromStringItem(index int) string {
if f.sharedStringTemp != nil {
if len(f.sharedStringItem) <= index {
return strconv.Itoa(index)
}
offsetRange := f.sharedStringItem[index]
if len(offsetRange) != 2 || offsetRange[0] > offsetRange[1] {
return strconv.Itoa(index)
}
buf := make([]byte, offsetRange[1]-offsetRange[0])
if _, err := f.sharedStringTemp.ReadAt(buf, int64(offsetRange[0])); err != nil {
return strconv.Itoa(index)
}
return string(buf)
}
needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings)
if needClose && err == nil {
defer func() {
err = tempFile.Close()
}()
}
f.sharedStringItem = [][]uint{}
f.sharedStringTemp, _ = os.CreateTemp(f.options.TmpDir, "excelize-")
f.tempFiles.Store(defaultTempFileSST, f.sharedStringTemp.Name())
var (
inElement string
i, offset uint
)
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
inElement = xmlElement.Name.Local
if inElement == "si" {
si := xlsxSI{}
_ = decoder.DecodeElement(&si, &xmlElement)
startIdx := offset
n, _ := f.sharedStringTemp.WriteString(si.String())
offset += uint(n)
f.sharedStringItem = append(f.sharedStringItem, []uint{startIdx, offset})
i++
}
}
}
return f.getFromStringItem(index)
}
// xmlDecoder creates XML decoder by given path in the zip from memory data
// or system temporary file.
func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) {
var (
content []byte
err error
tempFile *os.File
)
if content = f.readXML(name); len(content) > 0 {
return false, f.xmlNewDecoder(bytes.NewReader(content)), tempFile, err
}
tempFile, err = f.readTemp(name)
return true, f.xmlNewDecoder(tempFile), tempFile, err
}
// SetRowHeight provides a function to set the height of a single row. If the
// value of height is 0, will hide the specified row, if the value of height is
// -1, will unset the custom row height. For example, set the height of the
// first row in Sheet1:
//
// err := f.SetRowHeight("Sheet1", 1, 50)
func (f *File) SetRowHeight(sheet string, row int, height float64) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
if height > MaxRowHeight {
return ErrMaxRowHeight
}
if height < -1 {
return ErrParameterInvalid
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.prepareSheetXML(0, row)
rowIdx := row - 1
if height == -1 {
ws.SheetData.Row[rowIdx].Ht = nil
ws.SheetData.Row[rowIdx].CustomHeight = false
return err
}
ws.SheetData.Row[rowIdx].Ht = float64Ptr(height)
ws.SheetData.Row[rowIdx].CustomHeight = true
return err
}
// getRowHeight provides a function to get row height in pixels by given sheet
// name and row number.
func (f *File) getRowHeight(sheet string, row int) int {
ws, _ := f.workSheetReader(sheet)
ws.mu.Lock()
defer ws.mu.Unlock()
height := -1.0
for i := range ws.SheetData.Row {
v := &ws.SheetData.Row[i]
if v.R == row && v.Ht != nil {
height = *v.Ht
break
}
}
if height != -1.0 {
return int(convertRowHeightToPixels(height))
}
if ws.SheetFormatPr != nil && ws.SheetFormatPr.DefaultRowHeight > 0 {
return int(convertRowHeightToPixels(ws.SheetFormatPr.DefaultRowHeight))
}
// Optimization for when the row heights haven't changed.
return int(defaultRowHeightPixels)
}
// GetRowHeight provides a function to get row height by given worksheet name
// and row number. For example, get the height of the first row in Sheet1:
//
// height, err := f.GetRowHeight("Sheet1", 1)
func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
if row < 1 {
return defaultRowHeight, newInvalidRowNumberError(row)
}
ht := defaultRowHeight
ws, err := f.workSheetReader(sheet)
if err != nil {
return ht, err
}
if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
ht = ws.SheetFormatPr.DefaultRowHeight
}
if row > len(ws.SheetData.Row) {
return ht, nil // it will be better to use 0, but we take care with BC
}
for _, v := range ws.SheetData.Row {
if v.R == row && v.Ht != nil {
return *v.Ht, nil
}
}
// Optimization for when the row heights haven't changed.
return ht, nil
}
// sharedStringsReader provides a function to get the pointer to the structure
// after deserialization of xl/sharedStrings.xml.
func (f *File) sharedStringsReader() (*xlsxSST, error) {
var err error
f.mu.Lock()
defer f.mu.Unlock()
relPath := f.getWorkbookRelsPath()
if f.SharedStrings == nil {
var sharedStrings xlsxSST
ss := f.readXML(defaultXMLPathSharedStrings)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
Decode(&sharedStrings); err != nil && err != io.EOF {
return f.SharedStrings, err
}
if sharedStrings.Count == 0 {
sharedStrings.Count = len(sharedStrings.SI)
}
if sharedStrings.UniqueCount == 0 {
sharedStrings.UniqueCount = sharedStrings.Count
}
f.SharedStrings = &sharedStrings
for i := range sharedStrings.SI {
if sharedStrings.SI[i].T != nil {
f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i
}
}
if err = f.addContentTypePart(0, "sharedStrings"); err != nil {
return f.SharedStrings, err
}
rels, err := f.relsReader(relPath)
if err != nil {
return f.SharedStrings, err
}
for _, rel := range rels.Relationships {
if rel.Target == "/xl/sharedStrings.xml" {
return f.SharedStrings, nil
}
}
// Update workbook.xml.rels
f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "")
}
return f.SharedStrings, nil
}
// SetRowVisible provides a function to set visible of a single row by given
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
//
// err := f.SetRowVisible("Sheet1", 2, false)
func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.prepareSheetXML(0, row)
ws.SheetData.Row[row-1].Hidden = !visible
return nil
}
// GetRowVisible provides a function to get visible of a single row by given
// worksheet name and Excel row number. For example, get visible state of row
// 2 in Sheet1:
//
// visible, err := f.GetRowVisible("Sheet1", 2)
func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
if row < 1 {
return false, newInvalidRowNumberError(row)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return false, err
}
if row > len(ws.SheetData.Row) {
return false, nil
}
return !ws.SheetData.Row[row-1].Hidden, nil
}
// SetRowOutlineLevel provides a function to set outline level number of a
// single row by given worksheet name and row number. The range of 'level'
// parameter value from 1 to 7. For example, outline row 2 in Sheet1 to level 1:
//
// err := f.SetRowOutlineLevel("Sheet1", 2, 1)
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
if level > 7 || level < 1 {
return ErrOutlineLevel
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.prepareSheetXML(0, row)
ws.SheetData.Row[row-1].OutlineLevel = level
return nil
}
// GetRowOutlineLevel provides a function to get outline level number of a
// single row by given worksheet name and Excel row number. For example, get
// outline number of row 2 in Sheet1:
//
// level, err := f.GetRowOutlineLevel("Sheet1", 2)
func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
if row < 1 {
return 0, newInvalidRowNumberError(row)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return 0, err
}
if row > len(ws.SheetData.Row) {
return 0, nil
}
return ws.SheetData.Row[row-1].OutlineLevel, nil
}
// RemoveRow provides a function to remove single row by given worksheet name
// and Excel row number. For example, remove row 3 in Sheet1:
//
// err := f.RemoveRow("Sheet1", 3)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) RemoveRow(sheet string, row int) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.formulaSI.Clear()
if row > len(ws.SheetData.Row) {
return f.adjustHelper(sheet, rows, row, -1)
}
for rowIdx := range ws.SheetData.Row {
v := &ws.SheetData.Row[rowIdx]
if v.R == row {
for _, c := range v.C {
if err := f.removeFormula(&c, ws, sheet); err != nil {
return err
}
}
}
}
keep := 0
for rowIdx := range ws.SheetData.Row {
v := &ws.SheetData.Row[rowIdx]
if v.R != row {
ws.SheetData.Row[keep] = *v
keep++
}
}
ws.SheetData.Row = ws.SheetData.Row[:keep]
return f.adjustHelper(sheet, rows, row, -1)
}
// InsertRows provides a function to insert new rows after the given Excel row
// number starting from 1 and number of rows. For example, create two rows
// before row 3 in Sheet1:
//
// err := f.InsertRows("Sheet1", 3, 2)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) InsertRows(sheet string, row, n int) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
if row >= TotalRows || n >= TotalRows {
return ErrMaxRows
}
if n < 1 {
return ErrParameterInvalid
}
return f.adjustHelper(sheet, rows, row, n)
}
// DuplicateRow inserts a copy of specified row (by its Excel row number) below
//
// err := f.DuplicateRow("Sheet1", 2)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) DuplicateRow(sheet string, row int) error {
return f.DuplicateRowTo(sheet, row, row+1)
}
// DuplicateRowTo inserts a copy of specified row by it Excel number
// to specified row position moving down exists rows after target position
//
// err := f.DuplicateRowTo("Sheet1", 2, 7)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
if row < 1 {
return newInvalidRowNumberError(row)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if row2 < 1 || row == row2 {
return err
}
var ok bool
var rowCopy xlsxRow
for i, r := range ws.SheetData.Row {
if r.R == row {
err = deepcopy.Copy(&rowCopy, ws.SheetData.Row[i])
ok = true
break
}
}
if err := f.adjustHelper(sheet, rows, row2, 1); err != nil {
return err
}
if !ok {
return err
}
idx2 := -1
for i, r := range ws.SheetData.Row {
if r.R == row2 {
idx2 = i
break
}
}
rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...)
rowCopy.adjustSingleRowDimensions(row2 - row)
_ = f.adjustSingleRowFormulas(sheet, sheet, &rowCopy, row, row2-row, true)
if idx2 != -1 {
ws.SheetData.Row[idx2] = rowCopy
} else {
ws.SheetData.Row = append(ws.SheetData.Row, rowCopy)
}
for _, fn := range duplicateHelperFunc {
if err := fn(f, ws, sheet, row, row2); err != nil {
return err
}
}
return err
}
// duplicateSQRefHelper provides a function to adjust conditional formatting and
// data validations cell reference when duplicate rows.
func duplicateSQRefHelper(row, row2 int, ref string) (string, error) {
if !strings.Contains(ref, ":") {
ref += ":" + ref
}
abs := strings.Contains(ref, "$")
coordinates, err := rangeRefToCoordinates(ref)
if err != nil {
return "", err
}
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if y1 == y2 && y1 == row {
if ref, err = coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil {
return "", err
}
return ref, err
}
return "", err
}
// duplicateConditionalFormat create conditional formatting for the destination
// row if there are conditional formats in the copied row.
func (f *File) duplicateConditionalFormat(ws *xlsxWorksheet, sheet string, row, row2 int) error {
var cfs []*xlsxConditionalFormatting
for _, cf := range ws.ConditionalFormatting {
if cf != nil {
var SQRef []string
for _, ref := range strings.Split(cf.SQRef, " ") {
coordinates, err := duplicateSQRefHelper(row, row2, ref)
if err != nil {
return err
}
if coordinates != "" {
SQRef = append(SQRef, coordinates)
}
}
if len(SQRef) > 0 {
var cfCopy xlsxConditionalFormatting
_ = deepcopy.Copy(&cfCopy, *cf)
cfCopy.SQRef = strings.Join(SQRef, " ")
cfs = append(cfs, &cfCopy)
}
}
}
ws.ConditionalFormatting = append(ws.ConditionalFormatting, cfs...)
return nil
}
// duplicateDataValidations create data validations for the destination row if
// there are data validation rules in the copied row.
func (f *File) duplicateDataValidations(ws *xlsxWorksheet, sheet string, row, row2 int) error {
if ws.DataValidations == nil {
return nil
}
var dvs []*xlsxDataValidation
for _, dv := range ws.DataValidations.DataValidation {
if dv != nil {
var SQRef []string
for _, ref := range strings.Split(dv.Sqref, " ") {
coordinates, err := duplicateSQRefHelper(row, row2, ref)
if err != nil {
return err
}
if coordinates != "" {
SQRef = append(SQRef, coordinates)
}
}
if len(SQRef) > 0 {
var dvCopy xlsxDataValidation
_ = deepcopy.Copy(&dvCopy, *dv)
dvCopy.Sqref = strings.Join(SQRef, " ")
dvs = append(dvs, &dvCopy)
}
}
}
ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dvs...)
return nil
}
// duplicateMergeCells merge cells in the destination row if there are single
// row merged cells in the copied row.
func (f *File) duplicateMergeCells(ws *xlsxWorksheet, sheet string, row, row2 int) error {
if ws.MergeCells == nil {
return nil
}
if row > row2 {
row++
}
for _, rng := range ws.MergeCells.Cells {
coordinates, err := rangeRefToCoordinates(rng.Ref)
if err != nil {
return err
}
if coordinates[1] < row2 && row2 < coordinates[3] {
return nil
}
}
for i := 0; i < len(ws.MergeCells.Cells); i++ {
mergedCells := ws.MergeCells.Cells[i]
coordinates, _ := rangeRefToCoordinates(mergedCells.Ref)
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if y1 == y2 && y1 == row {
from, _ := CoordinatesToCellName(x1, row2)
to, _ := CoordinatesToCellName(x2, row2)
if err := f.MergeCell(sheet, from, to); err != nil {
return err
}
}
}
return nil
}
// checkRow provides a function to check and fill each column element for all
// rows and make that is continuous in a worksheet of XML. For example:
//
//
//
//
//
//
//
//
// in this case, we should to change it to
//
//
//
//
//
//
//
//
//
//
//
// Notice: this method could be very slow for large spreadsheets (more than
// 3000 rows one sheet).
func (ws *xlsxWorksheet) checkRow() error {
for rowIdx := range ws.SheetData.Row {
rowData := &ws.SheetData.Row[rowIdx]
colCount := len(rowData.C)
if colCount == 0 {
continue
}
// check and fill the cell without r attribute in a row element
rCount := 0
for idx, cell := range rowData.C {
rCount++
if cell.R != "" {
lastR, _, err := CellNameToCoordinates(cell.R)
if err != nil {
return err
}
if lastR > rCount {
rCount = lastR
}
continue
}
rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1)
}
lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R)
if err != nil {
return err
}
if colCount < lastCol {
sourceList := rowData.C
targetList := make([]xlsxC, 0, lastCol)
rowData.C = ws.SheetData.Row[rowIdx].C[:0]
for colIdx := 0; colIdx < lastCol; colIdx++ {
cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1)
if err != nil {
return err
}
targetList = append(targetList, xlsxC{R: cellName})
}
rowData.C = targetList
for colIdx := range sourceList {
colData := &sourceList[colIdx]
colNum, _, err := CellNameToCoordinates(colData.R)
if err != nil {
return err
}
ws.SheetData.Row[rowIdx].C[colNum-1] = *colData
}
}
}
return nil
}
// hasAttr determine if row non-default attributes.
func (r *xlsxRow) hasAttr() bool {
return r.Spans != "" || r.S != 0 || r.CustomFormat || r.Ht != nil ||
r.Hidden || r.CustomHeight || r.OutlineLevel != 0 || r.Collapsed ||
r.ThickTop || r.ThickBot || r.Ph
}
// SetRowStyle provides a function to set the style of rows by given worksheet
// name, row range, and style ID. Note that this will overwrite the existing
// styles for the rows, it won't append or merge style with existing styles.
//
// For example set style of row 1 on Sheet1:
//
// err := f.SetRowStyle("Sheet1", 1, 1, styleID)
//
// Set style of rows 1 to 10 on Sheet1:
//
// err := f.SetRowStyle("Sheet1", 1, 10, styleID)
func (f *File) SetRowStyle(sheet string, start, end, styleID int) error {
if end < start {
start, end = end, start
}
if start < 1 {
return newInvalidRowNumberError(start)
}
if end > TotalRows {
return ErrMaxRows
}
s, err := f.stylesReader()
if err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
return newInvalidStyleID(styleID)
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.prepareSheetXML(0, end)
for row := start - 1; row < end; row++ {
ws.SheetData.Row[row].S = styleID
ws.SheetData.Row[row].CustomFormat = true
for i := range ws.SheetData.Row[row].C {
if _, rowNum, err := CellNameToCoordinates(ws.SheetData.Row[row].C[i].R); err == nil && rowNum-1 == row {
ws.SheetData.Row[row].C[i].S = styleID
}
}
}
return nil
}
// convertRowHeightToPixels provides a function to convert the height of a
// cell from user's units to pixels. If the height hasn't been set by the user
// we use the default value. If the row is hidden it has a value of zero.
func convertRowHeightToPixels(height float64) float64 {
if height == 0 {
return 0
}
return math.Ceil(4.0 / 3.4 * height)
}
================================================
FILE: rows_test.go
================================================
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"path/filepath"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetRows(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
// Test get rows with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err := f.GetRows("Sheet1")
assert.NoError(t, err)
}
func TestRows(t *testing.T) {
const sheet2 = "Sheet2"
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test get rows with invalid sheet name
_, err = f.Rows("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
rows, err := f.Rows(sheet2)
assert.NoError(t, err)
var collectedRows [][]string
for rows.Next() {
columns, err := rows.Columns()
assert.NoError(t, err)
collectedRows = append(collectedRows, trimSliceSpace(columns))
}
if !assert.NoError(t, rows.Error()) {
t.FailNow()
}
assert.NoError(t, rows.Close())
returnedRows, err := f.GetRows(sheet2)
assert.NoError(t, err)
for i := range returnedRows {
returnedRows[i] = trimSliceSpace(returnedRows[i])
}
if !assert.Equal(t, collectedRows, returnedRows) {
t.FailNow()
}
assert.NoError(t, f.Close())
f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Rows("Sheet1")
assert.NoError(t, err)
// Test reload the file to memory from system temporary directory
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
value, err := f.GetCellValue("Sheet1", "A19")
assert.NoError(t, err)
assert.Equal(t, "Total:", value)
// Test load shared string table to memory
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.NoError(t, err)
value, err = f.GetCellValue("Sheet1", "A19")
assert.NoError(t, err)
assert.Equal(t, "A19", value)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRow.xlsx")))
assert.NoError(t, f.Close())
// Test rows iterator with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
rows, err = f.Rows(sheet2)
assert.NoError(t, err)
_, err = rows.Columns()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestRowsIterator(t *testing.T) {
sheetName, rowCount, expectedNumRow := "Sheet2", 0, 11
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)
rows, err := f.Rows(sheetName)
require.NoError(t, err)
for rows.Next() {
rowCount++
require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected")
}
assert.Equal(t, expectedNumRow, rowCount)
assert.NoError(t, rows.Close())
assert.NoError(t, f.Close())
// Valued cell sparse distribution test
f, sheetName, rowCount, expectedNumRow = NewFile(), "Sheet1", 0, 3
cells := []string{"C1", "E1", "A3", "B3", "C3", "D3", "E3"}
for _, cell := range cells {
assert.NoError(t, f.SetCellValue(sheetName, cell, 1))
}
rows, err = f.Rows(sheetName)
require.NoError(t, err)
for rows.Next() {
rowCount++
require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected")
}
assert.Equal(t, expectedNumRow, rowCount)
}
func TestRowsGetRowOpts(t *testing.T) {
sheetName := "Sheet2"
expectedRowStyleID1 := RowOpts{Height: 17.0, Hidden: false, StyleID: 1}
expectedRowStyleID2 := RowOpts{Height: 17.0, Hidden: false, StyleID: 0}
expectedRowStyleID3 := RowOpts{Height: 17.0, Hidden: false, StyleID: 2}
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)
rows, err := f.Rows(sheetName)
require.NoError(t, err)
assert.Equal(t, true, rows.Next())
_, err = rows.Columns()
require.NoError(t, err)
rowOpts := rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID1, rowOpts)
assert.Equal(t, true, rows.Next())
rowOpts = rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID2, rowOpts)
assert.Equal(t, true, rows.Next())
_, err = rows.Columns()
require.NoError(t, err)
rowOpts = rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID3, rowOpts)
}
func TestRowsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.Rows("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
}
func TestRowHeight(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
assert.EqualError(t, f.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), newInvalidRowNumberError(0).Error())
_, err := f.GetRowHeight("Sheet1", 0)
assert.EqualError(t, err, newInvalidRowNumberError(0).Error())
assert.NoError(t, f.SetRowHeight(sheet1, 1, 111.0))
height, err := f.GetRowHeight(sheet1, 1)
assert.NoError(t, err)
assert.Equal(t, 111.0, height)
// Test set row height overflow max row height limit
assert.EqualError(t, f.SetRowHeight(sheet1, 4, MaxRowHeight+1), ErrMaxRowHeight.Error())
// Test get row height that rows index over exists rows
height, err = f.GetRowHeight(sheet1, 5)
assert.NoError(t, err)
assert.Equal(t, defaultRowHeight, height)
// Test get row height that rows heights haven't changed
height, err = f.GetRowHeight(sheet1, 3)
assert.NoError(t, err)
assert.Equal(t, defaultRowHeight, height)
// Test set and get row height on not exists worksheet
assert.EqualError(t, f.SetRowHeight("SheetN", 1, 111.0), "sheet SheetN does not exist")
_, err = f.GetRowHeight("SheetN", 3)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test set row height with invalid sheet name
assert.EqualError(t, f.SetRowHeight("Sheet:1", 1, 10.0), ErrSheetNameInvalid.Error())
// Test get row height with invalid sheet name
_, err = f.GetRowHeight("Sheet:1", 3)
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
// Test get row height with custom default row height
assert.NoError(t, f.SetSheetProps(sheet1, &SheetPropsOptions{
DefaultRowHeight: float64Ptr(30.0),
CustomHeight: boolPtr(true),
}))
height, err = f.GetRowHeight(sheet1, 100)
assert.NoError(t, err)
assert.Equal(t, 30.0, height)
// Test set row height with custom default row height with prepare XML
assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10"))
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet2", "A2", true))
height, err = f.GetRowHeight("Sheet2", 1)
assert.NoError(t, err)
assert.Equal(t, 15.0, height)
err = f.SaveAs(filepath.Join("test", "TestRowHeight.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, 0.0, convertColWidthToPixels(0))
}
func TestColumns(t *testing.T) {
f := NewFile()
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
`)))
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
`)))
rows.curRow = 1
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
assert.True(t, rows.Next())
_, err = rows.Columns()
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
`)))
assert.True(t, rows.Next())
_, err = rows.Columns()
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
// Test token is nil
rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
_, err = rows.Columns()
assert.NoError(t, err)
}
func TestSharedStringsReader(t *testing.T) {
f := NewFile()
// Test read shared string with unsupported charset
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err := f.sharedStringsReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test read shared strings with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
_, err = f.sharedStringsReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test read shared strings with unsupported charset workbook relationships
f = NewFile()
f.Relationships.Delete(defaultXMLPathWorkbookRels)
f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset)
_, err = f.sharedStringsReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestRowVisibility(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, f.SetRowVisible("Sheet3", 2, true))
visible, err := f.GetRowVisible("Sheet3", 2)
assert.Equal(t, true, visible)
assert.NoError(t, err)
visible, err = f.GetRowVisible("Sheet3", 25)
assert.Equal(t, false, visible)
assert.NoError(t, err)
assert.EqualError(t, f.SetRowVisible("Sheet3", 0, true), newInvalidRowNumberError(0).Error())
assert.EqualError(t, f.SetRowVisible("SheetN", 2, false), "sheet SheetN does not exist")
// Test set row visibility with invalid sheet name
assert.EqualError(t, f.SetRowVisible("Sheet:1", 1, false), ErrSheetNameInvalid.Error())
visible, err = f.GetRowVisible("Sheet3", 0)
assert.Equal(t, false, visible)
assert.EqualError(t, err, newInvalidRowNumberError(0).Error())
_, err = f.GetRowVisible("SheetN", 1)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get row visibility with invalid sheet name
_, err = f.GetRowVisible("Sheet:1", 1)
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
}
func TestRemoveRow(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
r, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
const (
colCount = 10
rowCount = 10
)
assert.NoError(t, fillCells(f, sheet1, colCount, rowCount))
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.EqualError(t, f.RemoveRow(sheet1, -1), newInvalidRowNumberError(-1).Error())
assert.EqualError(t, f.RemoveRow(sheet1, 0), newInvalidRowNumberError(0).Error())
assert.NoError(t, f.RemoveRow(sheet1, 4))
assert.Len(t, r.SheetData.Row, rowCount-1)
assert.NoError(t, f.MergeCell(sheet1, "B3", "B5"))
assert.NoError(t, f.RemoveRow(sheet1, 2))
assert.Len(t, r.SheetData.Row, rowCount-2)
assert.NoError(t, f.RemoveRow(sheet1, 4))
assert.Len(t, r.SheetData.Row, rowCount-3)
err = f.AutoFilter(sheet1, "A2:A2", []AutoFilterOptions{{Column: "A", Expression: "x != blanks"}})
assert.NoError(t, err)
assert.NoError(t, f.RemoveRow(sheet1, 1))
assert.Len(t, r.SheetData.Row, rowCount-4)
assert.NoError(t, f.RemoveRow(sheet1, 2))
assert.Len(t, r.SheetData.Row, rowCount-5)
assert.NoError(t, f.RemoveRow(sheet1, 1))
assert.Len(t, r.SheetData.Row, rowCount-6)
assert.NoError(t, f.RemoveRow(sheet1, 10))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx")))
f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A1", "C1"))
assert.NoError(t, f.MergeCell("Sheet1", "A2", "C2"))
assert.NoError(t, f.RemoveRow("Sheet1", 1))
mergedCells, err := f.GetMergeCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "A1", mergedCells[0].GetStartAxis())
assert.Equal(t, "C1", mergedCells[0].GetEndAxis())
// Test remove row on not exist worksheet
assert.EqualError(t, f.RemoveRow("SheetN", 1), "sheet SheetN does not exist")
// Test remove row with invalid sheet name
assert.EqualError(t, f.RemoveRow("Sheet:1", 1), ErrSheetNameInvalid.Error())
f = NewFile()
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1",
FormulaOpts{Ref: &ref, Type: &formulaType}))
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.RemoveRow("Sheet1", 1), "XML syntax error on line 1: invalid UTF-8")
}
func TestInsertRows(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
r, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
const (
colCount = 10
rowCount = 10
)
assert.NoError(t, fillCells(f, sheet1, colCount, rowCount))
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.InsertRows(sheet1, 1, 1))
assert.Len(t, r.SheetData.Row, rowCount+1)
assert.NoError(t, f.InsertRows(sheet1, 4, 1))
assert.Len(t, r.SheetData.Row, rowCount+2)
assert.NoError(t, f.InsertRows(sheet1, 4, 2))
assert.Len(t, r.SheetData.Row, rowCount+4)
// Test insert rows with invalid sheet name
assert.EqualError(t, f.InsertRows("Sheet:1", 1, 1), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.InsertRows(sheet1, -1, 1), newInvalidRowNumberError(-1).Error())
assert.EqualError(t, f.InsertRows(sheet1, 0, 1), newInvalidRowNumberError(0).Error())
assert.EqualError(t, f.InsertRows(sheet1, 4, 0), ErrParameterInvalid.Error())
assert.EqualError(t, f.InsertRows(sheet1, 4, TotalRows), ErrMaxRows.Error())
assert.EqualError(t, f.InsertRows(sheet1, 4, TotalRows-5), ErrMaxRows.Error())
assert.EqualError(t, f.InsertRows(sheet1, TotalRows, 1), ErrMaxRows.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRows.xlsx")))
}
// Test internal structure state after insert operations. It is important
// for insert workflow to be constant to avoid side effect with functions
// related to internal structure.
func TestInsertRowsInEmptyFile(t *testing.T) {
f := NewFile()
sheet1 := f.GetSheetName(0)
r, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
assert.NoError(t, f.InsertRows(sheet1, 1, 1))
assert.Len(t, r.SheetData.Row, 0)
assert.NoError(t, f.InsertRows(sheet1, 2, 1))
assert.Len(t, r.SheetData.Row, 0)
assert.NoError(t, f.InsertRows(sheet1, 99, 1))
assert.Len(t, r.SheetData.Row, 0)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx")))
}
func prepareTestBook2() (*File, error) {
f := NewFile()
for cell, val := range map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
} {
if err := f.SetCellStr("Sheet1", cell, val); err != nil {
return f, err
}
}
return f, nil
}
func TestDuplicateRowFromSingleRow(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("FromSingleRow", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"]))
assert.NoError(t, f.DuplicateRow(sheet, 1))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_1"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A1"], "B2": cells["B1"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
assert.NoError(t, f.DuplicateRow(sheet, 2))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_2"))) {
t.FailNow()
}
expect = map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A1"], "B2": cells["B1"],
"A3": cells["A1"], "B3": cells["B1"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("UpdateDuplicatedRows", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"]))
assert.NoError(t, f.DuplicateRow(sheet, 1))
assert.NoError(t, f.SetCellStr(sheet, "A2", cells["A2"]))
assert.NoError(t, f.SetCellStr(sheet, "B2", cells["B2"]))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "UpdateDuplicatedRows"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A2"], "B2": cells["B2"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("FirstOfMultipleRows", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.DuplicateRow(sheet, 1))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A1"], "B2": cells["B1"],
"A3": cells["A2"], "B3": cells["B2"],
"A4": cells["A3"], "B4": cells["B3"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowZeroWithNoRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
t.Run("ZeroWithNoRows", func(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.DuplicateRow(sheet, 0), newInvalidRowNumberError(0).Error())
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "ZeroWithNoRows"))) {
t.FailNow()
}
val, err := f.GetCellValue(sheet, "A1")
assert.NoError(t, err)
assert.Empty(t, val)
val, err = f.GetCellValue(sheet, "B1")
assert.NoError(t, err)
assert.Empty(t, val)
val, err = f.GetCellValue(sheet, "A2")
assert.NoError(t, err)
assert.Empty(t, val)
val, err = f.GetCellValue(sheet, "B2")
assert.NoError(t, err)
assert.Empty(t, val)
assert.NoError(t, err)
expect := map[string]string{
"A1": "", "B1": "",
"A2": "", "B2": "",
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowMiddleRowOfEmptyFile(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
t.Run("MiddleRowOfEmptyFile", func(t *testing.T) {
f := NewFile()
assert.NoError(t, f.DuplicateRow(sheet, 99))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "MiddleRowOfEmptyFile"))) {
t.FailNow()
}
expect := map[string]string{
"A98": "",
"A99": "",
"A100": "",
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A2"], "B2": cells["B2"],
"A3": cells["A1"], "B3": cells["B1"],
"A4": cells["A3"], "B4": cells["B3"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A1"], "B1": cells["B1"],
"A2": cells["A2"], "B2": cells["B2"],
"A3": cells["A3"], "B3": cells["B3"],
"A7": cells["A1"], "B7": cells["B1"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowInsertBefore(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("InsertBefore", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
assert.NoError(t, f.DuplicateRowTo(sheet, 10, 4))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBefore"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A2"], "B1": cells["B2"],
"A2": cells["A1"], "B2": cells["B1"],
"A3": cells["A2"], "B3": cells["B2"],
"A5": cells["A3"], "B5": cells["B3"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v, cell) {
t.FailNow()
}
}
})
}
func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) {
t.FailNow()
}
expect := map[string]string{
"A1": cells["A3"], "B1": cells["B3"],
"A2": cells["A1"], "B2": cells["B1"],
"A3": cells["A2"], "B3": cells["B2"],
"A4": cells["A3"], "B4": cells["B3"],
}
for cell, val := range expect {
v, err := f.GetCellValue(sheet, cell)
assert.NoError(t, err)
if !assert.Equal(t, val, v) {
t.FailNow()
}
}
})
}
func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
f, err := prepareTestBook2()
assert.NoError(t, err)
assert.NoError(t, f.MergeCell(sheet, "B2", "C2"))
assert.NoError(t, f.MergeCell(sheet, "C6", "C8"))
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 8))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithMergeCells"))) {
t.FailNow()
}
expect := []MergeCell{
{"B3:C3", "B2 Value"},
{"C7:C10", ""},
{"B1:C1", "B2 Value"},
}
mergeCells, err := f.GetMergeCells(sheet)
assert.NoError(t, err)
for idx, val := range expect {
if !assert.Equal(t, val, mergeCells[idx]) {
t.FailNow()
}
}
})
}
func TestDuplicateRowInvalidRowNum(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.InvalidRowNum.%s.xlsx")
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 Value",
"A3": "A3 Value",
"B1": "B1 Value",
"B2": "B2 Value",
"B3": "B3 Value",
}
invalidIndexes := []int{-100, -2, -1, 0}
for _, row := range invalidIndexes {
name := fmt.Sprintf("%d", row)
t.Run(name, func(t *testing.T) {
f := NewFile()
for col, val := range cells {
assert.NoError(t, f.SetCellStr(sheet, col, val))
}
assert.EqualError(t, f.DuplicateRow(sheet, row), newInvalidRowNumberError(row).Error())
for col, val := range cells {
v, err := f.GetCellValue(sheet, col)
assert.NoError(t, err)
if !assert.Equal(t, val, v) {
t.FailNow()
}
}
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name)))
})
}
for _, row1 := range invalidIndexes {
for _, row2 := range invalidIndexes {
name := fmt.Sprintf("[%d,%d]", row1, row2)
t.Run(name, func(t *testing.T) {
f := NewFile()
for col, val := range cells {
assert.NoError(t, f.SetCellStr(sheet, col, val))
}
assert.EqualError(t, f.DuplicateRowTo(sheet, row1, row2), newInvalidRowNumberError(row1).Error())
for col, val := range cells {
v, err := f.GetCellValue(sheet, col)
assert.NoError(t, err)
if !assert.Equal(t, val, v) {
t.FailNow()
}
}
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name)))
})
}
}
}
func TestDuplicateRow(t *testing.T) {
f := NewFile()
// Test duplicate row with invalid sheet name
assert.EqualError(t, f.DuplicateRowTo("Sheet:1", 1, 2), ErrSheetNameInvalid.Error())
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$B$1",
}))
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "Amount+C1"))
assert.NoError(t, f.SetCellValue("Sheet1", "A10", "A10"))
format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
expected := []ConditionalFormatOptions{
{Type: "cell", Criteria: "greater than", Format: &format, Value: "0"},
}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1", expected))
dv := NewDataValidation(true)
dv.Sqref = "A1"
assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"}))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1"
assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10))
formula, err := f.GetCellFormula("Sheet1", "A10")
assert.NoError(t, err)
assert.Equal(t, "Amount+C10", formula)
value, err := f.GetCellValue("Sheet1", "A11")
assert.NoError(t, err)
assert.Equal(t, "A10", value)
cfs, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, cfs, 2)
assert.Equal(t, expected, cfs["A10:A10"])
dvs, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Len(t, dvs, 2)
assert.Equal(t, "A10:A10", dvs[1].Sqref)
// Test duplicate data validation with row number exceeds maximum limit
assert.Equal(t, ErrMaxRows, f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1))
// Test duplicate data validation with invalid range reference
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A"
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, 10))
// Test duplicate conditional formatting with row number exceeds maximum limit
assert.Equal(t, ErrMaxRows, f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1))
// Test duplicate conditional formatting with invalid range reference
ws.(*xlsxWorksheet).ConditionalFormatting[0].SQRef = "A"
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, 10))
}
func TestDuplicateRowTo(t *testing.T) {
f, sheetName := NewFile(), "Sheet1"
// Test duplicate row with invalid target row number
assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 0))
// Test duplicate row with equal source and target row number
assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 1))
// Test duplicate row on the blank worksheet
assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 2))
// Test duplicate row on the worksheet with illegal cell reference
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
})
assert.EqualError(t, f.DuplicateRowTo(sheetName, 1, 2), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test duplicate row on not exists worksheet
assert.EqualError(t, f.DuplicateRowTo("SheetN", 1, 2), "sheet SheetN does not exist")
// Test duplicate row with invalid sheet name
assert.EqualError(t, f.DuplicateRowTo("Sheet:1", 1, 2), ErrSheetNameInvalid.Error())
}
func TestDuplicateMergeCells(t *testing.T) {
f := File{}
ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{{Ref: "A1:-"}},
}}
assert.EqualError(t, f.duplicateMergeCells(ws, "Sheet1", 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
ws.MergeCells.Cells[0].Ref = "A1:B1"
assert.EqualError(t, f.duplicateMergeCells(ws, "SheetN", 1, 2), "sheet SheetN does not exist")
}
func TestGetValueFromInlineStr(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d, false)
assert.NoError(t, err)
assert.Empty(t, val)
}
func TestGetValueFromNumber(t *testing.T) {
c := &xlsxC{T: "n"}
f := NewFile()
d := &xlsxSST{}
for input, expected := range map[string]string{
"2.2.": "2.2.",
"1.1000000000000001": "1.1",
"2.2200000000000002": "2.22",
"28.552": "28.552",
"27.399000000000001": "27.399",
"26.245999999999999": "26.246",
"2422.3000000000002": "2422.3",
"2.220000ddsf0000000002-r": "2.220000ddsf0000000002-r",
} {
c.V = input
val, err := c.getValueFrom(f, d, false)
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
}
func TestErrSheetNotExistError(t *testing.T) {
assert.Equal(t, "sheet Sheet1 does not exist", ErrSheetNotExist{"Sheet1"}.Error())
}
func TestCheckRow(t *testing.T) {
f := NewFile()
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345
`))
_, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", false))
f = NewFile()
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345
`))
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.checked.Delete("xl/worksheets/sheet1.xml")
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), newCellNameToCoordinatesError("-", newInvalidCellNameError("-")).Error())
}
func TestSetRowStyle(t *testing.T) {
f := NewFile()
style1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"63BE7B"}, Pattern: 1}})
assert.NoError(t, err)
style2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1))
assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error())
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, TotalRows+1, style2), ErrMaxRows.Error())
// Test set row style with invalid style ID
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, -1), newInvalidStyleID(-1).Error())
// Test set row style with not exists style ID
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, 10), newInvalidStyleID(10).Error())
assert.EqualError(t, f.SetRowStyle("SheetN", 1, 1, style2), "sheet SheetN does not exist")
// Test set row style with invalid sheet name
assert.EqualError(t, f.SetRowStyle("Sheet:1", 1, 1, 0), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SetRowStyle("Sheet1", 5, 1, style2))
cellStyleID, err := f.GetCellStyle("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, style2, cellStyleID)
// Test cell inheritance rows style
assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil))
cellStyleID, err = f.GetCellStyle("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, style2, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx")))
// Test set row style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, cellStyleID), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetRowHeight(t *testing.T) {
f := NewFile()
// Test hidden row by set row height to 0
assert.NoError(t, f.SetRowHeight("Sheet1", 2, 0))
ht, err := f.GetRowHeight("Sheet1", 2)
assert.NoError(t, err)
assert.Empty(t, ht)
// Test unset custom row height
assert.NoError(t, f.SetRowHeight("Sheet1", 2, -1))
ht, err = f.GetRowHeight("Sheet1", 2)
assert.NoError(t, err)
assert.Equal(t, defaultRowHeight, ht)
// Test set row height with invalid height value
assert.Equal(t, ErrParameterInvalid, f.SetRowHeight("Sheet1", 2, -2))
}
func TestNumberFormats(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
cells := make([][]string, 0)
cols, err := f.Cols("Sheet2")
if !assert.NoError(t, err) {
t.FailNow()
}
for cols.Next() {
col, err := cols.Rows()
assert.NoError(t, err)
if err != nil {
break
}
cells = append(cells, col)
}
assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
assert.NoError(t, f.Close())
f = NewFile()
numFmt1, err := f.NewStyle(&Style{NumFmt: 1})
assert.NoError(t, err)
numFmt2, err := f.NewStyle(&Style{NumFmt: 2})
assert.NoError(t, err)
numFmt3, err := f.NewStyle(&Style{NumFmt: 3})
assert.NoError(t, err)
numFmt9, err := f.NewStyle(&Style{NumFmt: 9})
assert.NoError(t, err)
numFmt10, err := f.NewStyle(&Style{NumFmt: 10})
assert.NoError(t, err)
numFmt21, err := f.NewStyle(&Style{NumFmt: 21})
assert.NoError(t, err)
numFmt37, err := f.NewStyle(&Style{NumFmt: 37})
assert.NoError(t, err)
numFmt38, err := f.NewStyle(&Style{NumFmt: 38})
assert.NoError(t, err)
numFmt39, err := f.NewStyle(&Style{NumFmt: 39})
assert.NoError(t, err)
numFmt40, err := f.NewStyle(&Style{NumFmt: 40})
assert.NoError(t, err)
for _, cases := range [][]interface{}{
{"A1", numFmt1, 8.8888666665555493e+19, "88888666665555500000"},
{"A2", numFmt1, 8.8888666665555487, "9"},
{"A3", numFmt2, 8.8888666665555493e+19, "88888666665555500000.00"},
{"A4", numFmt2, 8.8888666665555487, "8.89"},
{"A5", numFmt3, 8.8888666665555493e+19, "88,888,666,665,555,500,000"},
{"A6", numFmt3, 8.8888666665555487, "9"},
{"A7", numFmt3, 123, "123"},
{"A8", numFmt3, -1234, "-1,234"},
{"A9", numFmt9, 8.8888666665555493e+19, "8888866666555550000000%"},
{"A10", numFmt9, -8.8888666665555493e+19, "-8888866666555550000000%"},
{"A11", numFmt9, 8.8888666665555487, "889%"},
{"A12", numFmt9, -8.8888666665555487, "-889%"},
{"A13", numFmt10, 8.8888666665555493e+19, "8888866666555550000000.00%"},
{"A14", numFmt10, -8.8888666665555493e+19, "-8888866666555550000000.00%"},
{"A15", numFmt10, 8.8888666665555487, "888.89%"},
{"A16", numFmt10, -8.8888666665555487, "-888.89%"},
{"A17", numFmt37, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
{"A18", numFmt37, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
{"A19", numFmt37, 8.8888666665555487, "9 "},
{"A20", numFmt37, -8.8888666665555487, "(9)"},
{"A21", numFmt38, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
{"A22", numFmt38, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
{"A23", numFmt38, 8.8888666665555487, "9 "},
{"A24", numFmt38, -8.8888666665555487, "(9)"},
{"A25", numFmt39, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
{"A26", numFmt39, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
{"A27", numFmt39, 8.8888666665555487, "8.89 "},
{"A28", numFmt39, -8.8888666665555487, "(8.89)"},
{"A29", numFmt40, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
{"A30", numFmt40, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
{"A31", numFmt40, 8.8888666665555487, "8.89 "},
{"A32", numFmt40, -8.8888666665555487, "(8.89)"},
{"A33", numFmt21, 44729.999988368058, "23:59:59"},
{"A34", numFmt21, 44944.375005787035, "09:00:00"},
{"A35", numFmt21, 44944.375005798611, "09:00:01"},
} {
cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string)
assert.NoError(t, f.SetCellStyle("Sheet1", cell, cell, styleID))
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
result, err := f.GetCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result, cell)
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
f = NewFile(Options{ShortDatePattern: "yyyy/m/d"})
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 43543.503206018519))
numFmt14, err := f.NewStyle(&Style{NumFmt: 14})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", numFmt14))
result, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "2019/3/19", result, "A1")
}
func TestCellXMLHandler(t *testing.T) {
var (
content = []byte(fmt.Sprintf(`10String
2*A10A32422.30000000000022022-10-22T15:05:29Z
`, NameSpaceSpreadSheet.Value))
expected, ws xlsxWorksheet
row *xlsxRow
)
assert.NoError(t, xml.Unmarshal(content, &expected))
decoder := xml.NewDecoder(bytes.NewReader(content))
rows := Rows{decoder: decoder}
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch element := token.(type) {
case xml.StartElement:
if element.Name.Local == "row" {
r, err := strconv.Atoi(element.Attr[0].Value)
assert.NoError(t, err)
ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: r})
row = &ws.SheetData.Row[len(ws.SheetData.Row)-1]
}
if element.Name.Local == "c" {
colCell := xlsxC{}
assert.NoError(t, colCell.cellXMLHandler(rows.decoder, &element))
row.C = append(row.C, colCell)
}
}
}
assert.Equal(t, expected.SheetData.Row, ws.SheetData.Row)
for _, rowXML := range []string{
`10
`, // s need number
`10
`, // missing
``, // incorrect data
} {
ws := xlsxWorksheet{}
content := []byte(fmt.Sprintf(`%s`, NameSpaceSpreadSheet.Value, rowXML))
expected := xml.Unmarshal(content, &ws)
assert.Error(t, expected)
decoder := xml.NewDecoder(bytes.NewReader(content))
rows := Rows{decoder: decoder}
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch element := token.(type) {
case xml.StartElement:
if element.Name.Local == "c" {
colCell := xlsxC{}
err := colCell.cellXMLHandler(rows.decoder, &element)
assert.Error(t, err)
assert.Equal(t, expected, err)
}
}
}
}
}
func BenchmarkRows(b *testing.B) {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
for i := 0; i < b.N; i++ {
rows, _ := f.Rows("Sheet2")
for rows.Next() {
row, _ := rows.Columns()
for i := range row {
if i >= 0 {
continue
}
}
}
if err := rows.Close(); err != nil {
b.Error(err)
}
}
if err := f.Close(); err != nil {
b.Error(err)
}
}
// trimSliceSpace trim continually blank element in the tail of slice.
func trimSliceSpace(s []string) []string {
for {
if len(s) > 0 && s[len(s)-1] == "" {
s = s[:len(s)-1]
} else {
break
}
}
return s
}
================================================
FILE: shape.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"strconv"
"strings"
)
// parseShapeOptions provides a function to parse the format settings of the
// shape with default value.
func parseShapeOptions(opts *Shape) (*Shape, error) {
if opts == nil {
return nil, ErrParameterInvalid
}
if opts.Type == "" {
return nil, ErrParameterInvalid
}
if opts.Width == 0 {
opts.Width = defaultShapeSize
}
if opts.Height == 0 {
opts.Height = defaultShapeSize
}
if opts.Line.Width == nil {
opts.Line.Width = float64Ptr(defaultShapeLineWidth)
}
if opts.Fill.Transparency < 0 || 100 < opts.Fill.Transparency {
return opts, ErrTransparency
}
format := opts.Format
graphicOptions, err := format.parseGraphicOptions(nil)
if err != nil {
return opts, err
}
opts.Format = *graphicOptions
return opts, err
}
// AddShape provides the method to add shape in a sheet by given worksheet
// name and shape format set (such as offset, scale, aspect ratio setting and
// print settings). For example, add text box (rect shape) in Sheet1:
//
// lineWidth := 1.2
// err := f.AddShape("Sheet1",
// &excelize.Shape{
// Cell: "G6",
// Type: "rect",
// Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth},
// Fill: excelize.Fill{Color: []string{"8EB9FF"}, Pattern: 1},
// Paragraph: []excelize.RichTextRun{
// {
// Text: "Rectangle Shape",
// Font: &excelize.Font{
// Bold: true,
// Italic: true,
// Family: "Times New Roman",
// Size: 18,
// Color: "777777",
// Underline: "sng",
// },
// },
// },
// Width: 180,
// Height: 40,
// },
// )
//
// The following shows the type of shape supported by excelize:
//
// accentBorderCallout1 (Callout 1 with Border and Accent Shape)
// accentBorderCallout2 (Callout 2 with Border and Accent Shape)
// accentBorderCallout3 (Callout 3 with Border and Accent Shape)
// accentCallout1 (Callout 1 Shape)
// accentCallout2 (Callout 2 Shape)
// accentCallout3 (Callout 3 Shape)
// actionButtonBackPrevious (Back or Previous Button Shape)
// actionButtonBeginning (Beginning Button Shape)
// actionButtonBlank (Blank Button Shape)
// actionButtonDocument (Document Button Shape)
// actionButtonEnd (End Button Shape)
// actionButtonForwardNext (Forward or Next Button Shape)
// actionButtonHelp (Help Button Shape)
// actionButtonHome (Home Button Shape)
// actionButtonInformation (Information Button Shape)
// actionButtonMovie (Movie Button Shape)
// actionButtonReturn (Return Button Shape)
// actionButtonSound (Sound Button Shape)
// arc (Curved Arc Shape)
// bentArrow (Bent Arrow Shape)
// bentConnector2 (Bent Connector 2 Shape)
// bentConnector3 (Bent Connector 3 Shape)
// bentConnector4 (Bent Connector 4 Shape)
// bentConnector5 (Bent Connector 5 Shape)
// bentUpArrow (Bent Up Arrow Shape)
// bevel (Bevel Shape)
// blockArc (Block Arc Shape)
// borderCallout1 (Callout 1 with Border Shape)
// borderCallout2 (Callout 2 with Border Shape)
// borderCallout3 (Callout 3 with Border Shape)
// bracePair (Brace Pair Shape)
// bracketPair (Bracket Pair Shape)
// callout1 (Callout 1 Shape)
// callout2 (Callout 2 Shape)
// callout3 (Callout 3 Shape)
// can (Can Shape)
// chartPlus (Chart Plus Shape)
// chartStar (Chart Star Shape)
// chartX (Chart X Shape)
// chevron (Chevron Shape)
// chord (Chord Shape)
// circularArrow (Circular Arrow Shape)
// cloud (Cloud Shape)
// cloudCallout (Callout Cloud Shape)
// corner (Corner Shape)
// cornerTabs (Corner Tabs Shape)
// cube (Cube Shape)
// curvedConnector2 (Curved Connector 2 Shape)
// curvedConnector3 (Curved Connector 3 Shape)
// curvedConnector4 (Curved Connector 4 Shape)
// curvedConnector5 (Curved Connector 5 Shape)
// curvedDownArrow (Curved Down Arrow Shape)
// curvedLeftArrow (Curved Left Arrow Shape)
// curvedRightArrow (Curved Right Arrow Shape)
// curvedUpArrow (Curved Up Arrow Shape)
// decagon (Decagon Shape)
// diagStripe (Diagonal Stripe Shape)
// diamond (Diamond Shape)
// dodecagon (Dodecagon Shape)
// donut (Donut Shape)
// doubleWave (Double Wave Shape)
// downArrow (Down Arrow Shape)
// downArrowCallout (Callout Down Arrow Shape)
// ellipse (Ellipse Shape)
// ellipseRibbon (Ellipse Ribbon Shape)
// ellipseRibbon2 (Ellipse Ribbon 2 Shape)
// flowChartAlternateProcess (Alternate Process Flow Shape)
// flowChartCollate (Collate Flow Shape)
// flowChartConnector (Connector Flow Shape)
// flowChartDecision (Decision Flow Shape)
// flowChartDelay (Delay Flow Shape)
// flowChartDisplay (Display Flow Shape)
// flowChartDocument (Document Flow Shape)
// flowChartExtract (Extract Flow Shape)
// flowChartInputOutput (Input Output Flow Shape)
// flowChartInternalStorage (Internal Storage Flow Shape)
// flowChartMagneticDisk (Magnetic Disk Flow Shape)
// flowChartMagneticDrum (Magnetic Drum Flow Shape)
// flowChartMagneticTape (Magnetic Tape Flow Shape)
// flowChartManualInput (Manual Input Flow Shape)
// flowChartManualOperation (Manual Operation Flow Shape)
// flowChartMerge (Merge Flow Shape)
// flowChartMultidocument (Multi-Document Flow Shape)
// flowChartOfflineStorage (Offline Storage Flow Shape)
// flowChartOffpageConnector (Off-Page Connector Flow Shape)
// flowChartOnlineStorage (Online Storage Flow Shape)
// flowChartOr (Or Flow Shape)
// flowChartPredefinedProcess (Predefined Process Flow Shape)
// flowChartPreparation (Preparation Flow Shape)
// flowChartProcess (Process Flow Shape)
// flowChartPunchedCard (Punched Card Flow Shape)
// flowChartPunchedTape (Punched Tape Flow Shape)
// flowChartSort (Sort Flow Shape)
// flowChartSummingJunction (Summing Junction Flow Shape)
// flowChartTerminator (Terminator Flow Shape)
// foldedCorner (Folded Corner Shape)
// frame (Frame Shape)
// funnel (Funnel Shape)
// gear6 (Gear 6 Shape)
// gear9 (Gear 9 Shape)
// halfFrame (Half Frame Shape)
// heart (Heart Shape)
// heptagon (Heptagon Shape)
// hexagon (Hexagon Shape)
// homePlate (Home Plate Shape)
// horizontalScroll (Horizontal Scroll Shape)
// irregularSeal1 (Irregular Seal 1 Shape)
// irregularSeal2 (Irregular Seal 2 Shape)
// leftArrow (Left Arrow Shape)
// leftArrowCallout (Callout Left Arrow Shape)
// leftBrace (Left Brace Shape)
// leftBracket (Left Bracket Shape)
// leftCircularArrow (Left Circular Arrow Shape)
// leftRightArrow (Left Right Arrow Shape)
// leftRightArrowCallout (Callout Left Right Arrow Shape)
// leftRightCircularArrow (Left Right Circular Arrow Shape)
// leftRightRibbon (Left Right Ribbon Shape)
// leftRightUpArrow (Left Right Up Arrow Shape)
// leftUpArrow (Left Up Arrow Shape)
// lightningBolt (Lightning Bolt Shape)
// line (Line Shape)
// lineInv (Line Inverse Shape)
// mathDivide (Divide Math Shape)
// mathEqual (Equal Math Shape)
// mathMinus (Minus Math Shape)
// mathMultiply (Multiply Math Shape)
// mathNotEqual (Not Equal Math Shape)
// mathPlus (Plus Math Shape)
// moon (Moon Shape)
// nonIsoscelesTrapezoid (Non-Isosceles Trapezoid Shape)
// noSmoking (No Smoking Shape)
// notchedRightArrow (Notched Right Arrow Shape)
// octagon (Octagon Shape)
// parallelogram (Parallelogram Shape)
// pentagon (Pentagon Shape)
// pie (Pie Shape)
// pieWedge (Pie Wedge Shape)
// plaque (Plaque Shape)
// plaqueTabs (Plaque Tabs Shape)
// plus (Plus Shape)
// quadArrow (Quad-Arrow Shape)
// quadArrowCallout (Callout Quad-Arrow Shape)
// rect (Rectangle Shape)
// ribbon (Ribbon Shape)
// ribbon2 (Ribbon 2 Shape)
// rightArrow (Right Arrow Shape)
// rightArrowCallout (Callout Right Arrow Shape)
// rightBrace (Right Brace Shape)
// rightBracket (Right Bracket Shape)
// round1Rect (One Round Corner Rectangle Shape)
// round2DiagRect (Two Diagonal Round Corner Rectangle Shape)
// round2SameRect (Two Same-side Round Corner Rectangle Shape)
// roundRect (Round Corner Rectangle Shape)
// rtTriangle (Right Triangle Shape)
// smileyFace (Smiley Face Shape)
// snip1Rect (One Snip Corner Rectangle Shape)
// snip2DiagRect (Two Diagonal Snip Corner Rectangle Shape)
// snip2SameRect (Two Same-side Snip Corner Rectangle Shape)
// snipRoundRect (One Snip One Round Corner Rectangle Shape)
// squareTabs (Square Tabs Shape)
// star10 (Ten Pointed Star Shape)
// star12 (Twelve Pointed Star Shape)
// star16 (Sixteen Pointed Star Shape)
// star24 (Twenty Four Pointed Star Shape)
// star32 (Thirty Two Pointed Star Shape)
// star4 (Four Pointed Star Shape)
// star5 (Five Pointed Star Shape)
// star6 (Six Pointed Star Shape)
// star7 (Seven Pointed Star Shape)
// star8 (Eight Pointed Star Shape)
// straightConnector1 (Straight Connector 1 Shape)
// stripedRightArrow (Striped Right Arrow Shape)
// sun (Sun Shape)
// swooshArrow (Swoosh Arrow Shape)
// teardrop (Teardrop Shape)
// trapezoid (Trapezoid Shape)
// triangle (Triangle Shape)
// upArrow (Up Arrow Shape)
// upArrowCallout (Callout Up Arrow Shape)
// upDownArrow (Up Down Arrow Shape)
// upDownArrowCallout (Callout Up Down Arrow Shape)
// uturnArrow (U-Turn Arrow Shape)
// verticalScroll (Vertical Scroll Shape)
// wave (Wave Shape)
// wedgeEllipseCallout (Callout Wedge Ellipse Shape)
// wedgeRectCallout (Callout Wedge Rectangle Shape)
// wedgeRoundRectCallout (Callout Wedge Round Rectangle Shape)
//
// The following shows the type of text underline supported by excelize:
//
// none
// words
// sng
// dbl
// heavy
// dotted
// dottedHeavy
// dash
// dashHeavy
// dashLong
// dashLongHeavy
// dotDash
// dotDashHeavy
// dotDotDash
// dotDotDashHeavy
// wavy
// wavyHeavy
// wavyDbl
func (f *File) AddShape(sheet string, opts *Shape) error {
options, err := parseShapeOptions(opts)
if err != nil {
return err
}
// Read sheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
// Add first shape for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
drawingID := f.countDrawings() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
if ws.Drawing != nil {
// The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
} else {
// Add first shape for given sheet.
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetDrawing(sheet, rID)
f.addSheetNameSpace(sheet, SourceRelationship)
}
if err = f.addDrawingShape(sheet, drawingXML, opts.Cell, options); err != nil {
return err
}
return f.addContentTypePart(drawingID, "drawings")
}
// cellAnchorShape create a two cell anchor shape size placeholder for a
// group, a shape, or a drawing element.
func (f *File) cellAnchorShape(sheet, drawingXML, cell string, width, height uint, format GraphicOptions) (*xlsxWsDr, *xdrCellAnchor, int, error) {
fromCol, fromRow, err := CellNameToCoordinates(cell)
if err != nil {
return nil, nil, 0, err
}
w := int(float64(width) * format.ScaleX)
h := int(float64(height) * format.ScaleY)
colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, w, h, &format)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return content, nil, cNvPrID, err
}
cellAnchor := xdrCellAnchor{}
from := xlsxFrom{}
from.Col = colStart
from.ColOff = x1 * EMU
from.Row = rowStart
from.RowOff = y1 * EMU
cellAnchor.From = &from
if format.Positioning != "oneCell" {
to := xlsxTo{}
to.Col = colEnd
to.ColOff = x2 * EMU
to.Row = rowEnd
to.RowOff = y2 * EMU
cellAnchor.To = &to
cellAnchor.EditAs = format.Positioning
}
if format.Positioning == "oneCell" {
cellAnchor.Ext = &xlsxPositiveSize2D{
Cx: x2 * EMU,
Cy: y2 * EMU,
}
}
return content, &cellAnchor, cNvPrID, err
}
// addDrawingShape provides a function to add preset geometry by given sheet,
// drawingXML and format sets.
func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error {
content, cellAnchor, cNvPrID, err := f.cellAnchorShape(
sheet, drawingXML, cell, opts.Width, opts.Height, opts.Format)
if err != nil {
return err
}
var solidColor string
if len(opts.Fill.Color) == 1 {
solidColor = strings.ReplaceAll(strings.ToUpper(opts.Fill.Color[0]), "#", "")
}
shape := xdrSp{
Macro: opts.Macro,
NvSpPr: &xdrNvSpPr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Name: "Shape " + strconv.Itoa(cNvPrID),
Descr: opts.Format.AltText,
},
CNvSpPr: &xdrCNvSpPr{
TxBox: true,
},
},
SpPr: &xlsxSpPr{
Xfrm: xlsxXfrm{
Ext: xlsxPositiveSize2D{
Cx: int(opts.Width) * EMU,
Cy: int(opts.Height) * EMU,
},
},
PrstGeom: xlsxPrstGeom{
Prst: opts.Type,
},
},
Style: &xdrStyle{
LnRef: setShapeRef(opts.Line.Color, 2),
FillRef: setShapeRef(solidColor, 1),
EffectRef: setShapeRef("", 0),
FontRef: &aFontRef{
Idx: "minor",
SchemeClr: &attrValString{
Val: stringPtr("tx1"),
},
},
},
TxBody: &xdrTxBody{
BodyPr: &aBodyPr{
VertOverflow: "clip",
HorzOverflow: "clip",
Wrap: "none",
RtlCol: false,
Anchor: "t",
},
},
}
if len(opts.Format.Name) > 0 {
shape.NvSpPr.CNvPr.Name = opts.Format.Name
}
if *opts.Line.Width != 1 {
shape.SpPr.Ln = xlsxLineProperties{
W: f.ptToEMUs(*opts.Line.Width),
}
}
if opts.Fill.Transparency > 0 {
val := (100 - opts.Fill.Transparency) * 1000
shape.SpPr.SolidFill = &aSolidFill{SrgbClr: &aSrgbClr{
Val: stringPtr(solidColor),
Alpha: &attrValInt{Val: &val},
}}
}
defaultFont, err := f.GetDefaultFont()
if err != nil {
return err
}
if len(opts.Paragraph) < 1 {
opts.Paragraph = []RichTextRun{
{
Font: &Font{
Bold: false,
Italic: false,
Underline: "none",
Family: defaultFont,
Size: 11,
Color: "000000",
},
Text: " ",
},
}
}
for _, p := range opts.Paragraph {
u := "none"
font := &Font{}
if p.Font != nil {
font = p.Font
}
if idx := inStrSlice(supportedDrawingUnderlineTypes, font.Underline, true); idx != -1 {
u = supportedDrawingUnderlineTypes[idx]
}
text := p.Text
if text == "" {
text = " "
}
paragraph := &aP{
R: &aR{
RPr: aRPr{
I: font.Italic,
B: font.Bold,
Lang: "en-US",
AltLang: "en-US",
U: u,
Sz: font.Size * 100,
Latin: &xlsxCTTextFont{Typeface: font.Family},
Ea: &xlsxCTTextFont{Typeface: font.Family},
Cs: &xlsxCTTextFont{Typeface: font.Family},
},
T: text,
},
EndParaRPr: &aEndParaRPr{
Lang: "en-US",
},
}
srgbClr := strings.ReplaceAll(strings.ToUpper(font.Color), "#", "")
if len(srgbClr) == 6 {
paragraph.R.RPr.SolidFill = &aSolidFill{
SrgbClr: &aSrgbClr{
Val: stringPtr(srgbClr),
},
}
}
shape.TxBody.P = append(shape.TxBody.P, paragraph)
}
cellAnchor.Sp = &shape
cellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Format.Locked,
FPrintsWithSheet: *opts.Format.PrintObject,
}
if opts.Format.Positioning == "oneCell" {
content.OneCellAnchor = append(content.OneCellAnchor, cellAnchor)
} else {
content.TwoCellAnchor = append(content.TwoCellAnchor, cellAnchor)
}
f.Drawings.Store(drawingXML, content)
return err
}
// setShapeRef provides a function to set color with hex model by given actual
// color value.
func setShapeRef(color string, i int) *aRef {
if color == "" {
return &aRef{
Idx: 0,
ScrgbClr: &aScrgbClr{
R: 0,
G: 0,
B: 0,
},
}
}
return &aRef{
Idx: i,
SrgbClr: &attrValString{
Val: stringPtr(strings.ReplaceAll(strings.ToUpper(color), "#", "")),
},
}
}
================================================
FILE: shape_test.go
================================================
package excelize
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddShape(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddShape("Sheet1", &Shape{
Cell: "A30",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}))
assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}))
shape1 := Shape{Cell: "C30", Type: "rect", Width: 160, Height: 160}
assert.NoError(t, f.AddShape("Sheet1", &shape1))
// Test add shape with invalid positioning types
assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning), f.AddShape("Sheet1", &Shape{Cell: "C30", Type: "rect", Format: GraphicOptions{Positioning: "x"}}))
assert.EqualError(t, f.AddShape("Sheet3", &Shape{Cell: "C30", Type: "rect"}), "sheet Sheet3 does not exist")
assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet3", nil))
assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet1", &Shape{Cell: "A1"}))
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddShape("Sheet1", &Shape{
Cell: "A",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))
// Test add first shape for given sheet
f = NewFile()
lineWidth := 1.2
shape2 := Shape{
Cell: "A1",
Type: "ellipseRibbon",
Line: ShapeLine{Color: "4286F4", Width: &lineWidth},
Fill: Fill{Color: []string{"8EB9FF"}, Transparency: 60},
Format: GraphicOptions{
AltText: "Shape",
Name: "Shape 1",
PrintObject: boolPtr(true),
Locked: boolPtr(false),
ScaleX: 0.8,
ScaleY: 0.8,
Positioning: "oneCell",
},
Paragraph: []RichTextRun{
{
Font: &Font{
Bold: true,
Italic: true,
Family: "Times New Roman",
Size: 18,
Color: "777777",
Underline: "sng",
},
Text: "Shape",
},
},
Height: 90,
}
assert.NoError(t, f.AddShape("Sheet1", &shape2))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test add shape with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.AddShape("Sheet:1", &Shape{
Cell: "A30",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}))
// Test add shape with transparency value exceeds limit
assert.Equal(t, ErrTransparency, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Fill: Fill{Color: []string{"8EB9FF"}, Transparency: 110}}))
// Test add shape with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
// Test add shape with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddDrawingShape(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1",
&Shape{
Width: defaultShapeSize,
Height: defaultShapeSize,
Format: GraphicOptions{
PrintObject: boolPtr(true),
Locked: boolPtr(false),
},
},
), "XML syntax error on line 1: invalid UTF-8")
}
================================================
FILE: sheet.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/tiendc/go-deepcopy"
)
// IgnoredErrorsType is the type of ignored errors.
type IgnoredErrorsType byte
// Ignored errors types enumeration.
const (
IgnoredErrorsEvalError = iota
IgnoredErrorsTwoDigitTextYear
IgnoredErrorsNumberStoredAsText
IgnoredErrorsFormula
IgnoredErrorsFormulaRange
IgnoredErrorsUnlockedFormula
IgnoredErrorsEmptyCellReference
IgnoredErrorsListDataValidation
IgnoredErrorsCalculatedColumn
)
// NewSheet provides the function to create a new sheet by given a worksheet
// name and returns the index of the sheets in the workbook after it appended.
// Note that when creating a new workbook, the default worksheet named
// `Sheet1` will be created.
func (f *File) NewSheet(sheet string) (int, error) {
var err error
if err = checkSheetName(sheet); err != nil {
return -1, err
}
// Check if the worksheet already exists
index, err := f.GetSheetIndex(sheet)
if index != -1 {
return index, err
}
_ = f.DeleteSheet(sheet)
f.SheetCount++
wb, _ := f.workbookReader()
sheetID := 0
for _, v := range wb.Sheets.Sheet {
if v.SheetID > sheetID {
sheetID = v.SheetID
}
}
sheetID++
// Update [Content_Types].xml
_ = f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet)
// Create new sheet /xl/worksheets/sheet%d.xml
f.setSheet(sheetID, sheet)
// Update workbook.xml.rels
rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("worksheets/sheet%d.xml", sheetID), "")
// Update workbook.xml
f.setWorkbook(sheet, sheetID, rID)
return f.GetSheetIndex(sheet)
}
// contentTypesReader provides a function to get the pointer to the
// [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() (*xlsxTypes, error) {
if f.ContentTypes == nil {
f.ContentTypes = new(xlsxTypes)
f.ContentTypes.mu.Lock()
defer f.ContentTypes.mu.Unlock()
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))).
Decode(f.ContentTypes); err != nil && err != io.EOF {
return f.ContentTypes, err
}
}
return f.ContentTypes, nil
}
// contentTypesWriter provides a function to save [Content_Types].xml after
// serialize structure.
func (f *File) contentTypesWriter() {
if f.ContentTypes != nil {
output, _ := xml.Marshal(f.ContentTypes)
f.saveFileList(defaultXMLPathContentTypes, output)
}
}
// getWorksheetPath construct a target XML as xl/worksheets/sheet%d by split
// path, compatible with different types of relative paths in
// workbook.xml.rels, for example: worksheets/sheet%d.xml
// and /xl/worksheets/sheet%d.xml
func (f *File) getWorksheetPath(relTarget string) (path string) {
path = filepath.ToSlash(strings.TrimPrefix(
strings.ReplaceAll(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), relTarget)), "\\", "/"), "/"))
if strings.HasPrefix(relTarget, "/") {
path = filepath.ToSlash(strings.TrimPrefix(strings.ReplaceAll(filepath.Clean(relTarget), "\\", "/"), "/"))
}
return path
}
// mergeExpandedCols merge expanded columns.
func (f *File) mergeExpandedCols(ws *xlsxWorksheet) {
sort.Slice(ws.Cols.Col, func(i, j int) bool {
return ws.Cols.Col[i].Min < ws.Cols.Col[j].Min
})
var columns []xlsxCol
for i, n := 0, len(ws.Cols.Col); i < n; {
left := i
for i++; i < n && reflect.DeepEqual(
xlsxCol{
BestFit: ws.Cols.Col[i-1].BestFit,
Collapsed: ws.Cols.Col[i-1].Collapsed,
CustomWidth: ws.Cols.Col[i-1].CustomWidth,
Hidden: ws.Cols.Col[i-1].Hidden,
Max: ws.Cols.Col[i-1].Max + 1,
Min: ws.Cols.Col[i-1].Min + 1,
OutlineLevel: ws.Cols.Col[i-1].OutlineLevel,
Phonetic: ws.Cols.Col[i-1].Phonetic,
Style: ws.Cols.Col[i-1].Style,
Width: ws.Cols.Col[i-1].Width,
}, ws.Cols.Col[i]); i++ {
}
var column xlsxCol
_ = deepcopy.Copy(&column, ws.Cols.Col[left])
if left < i-1 {
column.Max = ws.Cols.Col[i-1].Min
}
columns = append(columns, column)
}
ws.Cols.Col = columns
}
// workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after
// serialize structure.
func (f *File) workSheetWriter() {
var (
arr []byte
buffer = bytes.NewBuffer(arr)
encoder = xml.NewEncoder(buffer)
)
f.Sheet.Range(func(p, ws interface{}) bool {
if ws != nil {
sheet := ws.(*xlsxWorksheet)
if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 {
_ = sheet.mergeOverlapCells()
}
if sheet.Cols != nil && len(sheet.Cols.Col) > 0 {
f.mergeExpandedCols(sheet)
}
sheet.SheetData.Row = trimRow(&sheet.SheetData)
if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil {
f.addNameSpaces(p.(string), SourceRelationship)
}
if sheet.DecodeAlternateContent != nil {
sheet.AlternateContent = &xlsxAlternateContent{
Content: sheet.DecodeAlternateContent.Content,
XMLNSMC: SourceRelationshipCompatibility.Value,
}
}
sheet.DecodeAlternateContent = nil
// reusing buffer
_ = encoder.Encode(sheet)
f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes())))
_, ok := f.checked.Load(p.(string))
if ok {
f.Sheet.Delete(p.(string))
f.checked.Delete(p.(string))
}
buffer.Reset()
}
return true
})
}
// trimRow provides a function to trim empty rows.
func trimRow(sheetData *xlsxSheetData) []xlsxRow {
var (
row xlsxRow
i int
)
for k := 0; k < len(sheetData.Row); k++ {
row = sheetData.Row[k]
if row = trimCell(row); len(row.C) != 0 || row.hasAttr() {
sheetData.Row[i] = row
i++
continue
}
sheetData.Row = append(sheetData.Row[:k], sheetData.Row[k+1:]...)
k--
}
return sheetData.Row[:i]
}
// trimCell provides a function to trim blank cells which created by fillColumns.
func trimCell(row xlsxRow) xlsxRow {
column := row.C
rowFull := true
for i := range column {
rowFull = column[i].hasValue() && rowFull
}
if rowFull {
return row
}
i := 0
for _, c := range column {
if c.hasValue() {
row.C[i] = c
i++
}
}
row.C = row.C[:i]
return row
}
// setContentTypes provides a function to read and update property of contents
// type of the spreadsheet.
func (f *File) setContentTypes(partName, contentType string) error {
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
content.Overrides = append(content.Overrides, xlsxOverride{
PartName: partName,
ContentType: contentType,
})
return err
}
// setSheet provides a function to update sheet property by given index.
func (f *File) setSheet(index int, name string) {
ws := xlsxWorksheet{
Dimension: &xlsxDimension{Ref: "A1"},
SheetViews: &xlsxSheetViews{
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
},
}
sheetXMLPath := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
f.sheetMap[name] = sheetXMLPath
f.Sheet.Store(sheetXMLPath, &ws)
f.xmlAttr.Store(sheetXMLPath, []xml.Attr{NameSpaceSpreadSheet})
}
// relsWriter provides a function to save relationships after
// serialize structure.
func (f *File) relsWriter() {
f.Relationships.Range(func(path, rel interface{}) bool {
if rel != nil {
output, _ := xml.Marshal(rel.(*xlsxRelationships))
if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") {
output = f.replaceNameSpaceBytes(path.(string), output)
}
f.saveFileList(path.(string), replaceRelationshipsBytes(output))
}
return true
})
}
// replaceRelationshipsBytes; Some tools that read spreadsheet files have very
// strict requirements about the structure of the input XML. This function is
// a horrible hack to fix that after the XML marshalling is completed.
func replaceRelationshipsBytes(content []byte) []byte {
sourceXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`)
targetXmlns := []byte("r")
return bytesReplace(content, sourceXmlns, targetXmlns, -1)
}
// SetActiveSheet provides a function to set the default active sheet of the
// workbook by a given index. Note that the active index is different from the
// ID returned by function GetSheetMap(). It should be greater than or equal to 0
// and less than the total worksheet numbers.
func (f *File) SetActiveSheet(index int) {
if index < 0 {
index = 0
}
wb, _ := f.workbookReader()
for activeTab := range wb.Sheets.Sheet {
if activeTab == index {
if wb.BookViews == nil {
wb.BookViews = &xlsxBookViews{}
}
if len(wb.BookViews.WorkBookView) > 0 {
wb.BookViews.WorkBookView[0].ActiveTab = activeTab
} else {
wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{
ActiveTab: activeTab,
})
}
}
}
for idx, name := range f.GetSheetList() {
ws, err := f.workSheetReader(name)
if err != nil {
// Chartsheet, macrosheet or dialogsheet
return
}
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
}
}
if len(ws.SheetViews.SheetView) > 0 {
ws.SheetViews.SheetView[0].TabSelected = false
}
if index == idx {
if len(ws.SheetViews.SheetView) > 0 {
ws.SheetViews.SheetView[0].TabSelected = true
} else {
ws.SheetViews.SheetView = append(ws.SheetViews.SheetView, xlsxSheetView{
TabSelected: true,
})
}
}
}
}
// GetActiveSheetIndex provides a function to get active sheet index of the
// spreadsheet. If not found the active sheet will be return integer 0.
func (f *File) GetActiveSheetIndex() (index int) {
sheetID := f.getActiveSheetID()
wb, _ := f.workbookReader()
if wb != nil {
for idx, sheet := range wb.Sheets.Sheet {
if sheet.SheetID == sheetID {
index = idx
return
}
}
}
return
}
// getActiveSheetID provides a function to get active sheet ID of the
// spreadsheet. If not found the active sheet will be return integer 0.
func (f *File) getActiveSheetID() int {
wb, _ := f.workbookReader()
if wb != nil {
if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 {
activeTab := wb.BookViews.WorkBookView[0].ActiveTab
if len(wb.Sheets.Sheet) > activeTab && wb.Sheets.Sheet[activeTab].SheetID != 0 {
return wb.Sheets.Sheet[activeTab].SheetID
}
}
if len(wb.Sheets.Sheet) >= 1 {
return wb.Sheets.Sheet[0].SheetID
}
}
return 0
}
// SetSheetName provides a function to set the worksheet name by given source and
// target worksheet names. Maximum 31 characters are allowed in sheet title and
// this function only changes the name of the sheet and will not update the
// sheet name in the formula or reference associated with the cell. So there
// may be problem formula error or reference missing.
func (f *File) SetSheetName(source, target string) error {
var err error
if err = checkSheetName(source); err != nil {
return err
}
if err = checkSheetName(target); err != nil {
return err
}
if target == source {
return err
}
f.clearCalcCache()
wb, _ := f.workbookReader()
for k, v := range wb.Sheets.Sheet {
if v.Name == source {
wb.Sheets.Sheet[k].Name = target
f.sheetMap[target] = f.sheetMap[source]
delete(f.sheetMap, source)
}
}
if wb.DefinedNames == nil {
return err
}
for i, dn := range wb.DefinedNames.DefinedName {
wb.DefinedNames.DefinedName[i].Data = adjustRangeSheetName(dn.Data, source, target)
}
return err
}
// GetSheetName provides a function to get the sheet name of the workbook by
// the given sheet index. If the given sheet index is invalid, it will return
// an empty string.
func (f *File) GetSheetName(index int) (name string) {
for idx, sheet := range f.GetSheetList() {
if idx == index {
name = sheet
return
}
}
return
}
// getSheetID provides a function to get worksheet ID of the spreadsheet by
// given sheet name. If given worksheet name is invalid, will return an
// integer type value -1.
func (f *File) getSheetID(sheet string) int {
for sheetID, name := range f.GetSheetMap() {
if strings.EqualFold(name, sheet) {
return sheetID
}
}
return -1
}
// GetSheetIndex provides a function to get a sheet index of the workbook by
// the given sheet name. If the given sheet name is invalid or sheet doesn't
// exist, it will return an integer type value -1.
func (f *File) GetSheetIndex(sheet string) (int, error) {
if err := checkSheetName(sheet); err != nil {
return -1, err
}
for index, name := range f.GetSheetList() {
if strings.EqualFold(name, sheet) {
return index, nil
}
}
return -1, nil
}
// GetSheetMap provides a function to get worksheets, chart sheets, dialog
// sheets ID and name map of the workbook. For example:
//
// f, err := excelize.OpenFile("Book1.xlsx")
// if err != nil {
// return
// }
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// for index, name := range f.GetSheetMap() {
// fmt.Println(index, name)
// }
func (f *File) GetSheetMap() map[int]string {
wb, _ := f.workbookReader()
sheetMap := map[int]string{}
if wb != nil {
for _, sheet := range wb.Sheets.Sheet {
sheetMap[sheet.SheetID] = sheet.Name
}
}
return sheetMap
}
// GetSheetList provides a function to get worksheets, chart sheets, and
// dialog sheets name list of the workbook.
func (f *File) GetSheetList() (list []string) {
wb, _ := f.workbookReader()
if wb != nil {
for _, sheet := range wb.Sheets.Sheet {
list = append(list, sheet.Name)
}
}
return
}
// getSheetMap provides a function to get worksheet name and XML file path map
// of the spreadsheet.
func (f *File) getSheetMap() (map[string]string, error) {
maps := map[string]string{}
wb, err := f.workbookReader()
if err != nil {
return nil, err
}
rels, err := f.relsReader(f.getWorkbookRelsPath())
if err != nil {
return nil, err
}
if rels == nil {
return maps, nil
}
for _, v := range wb.Sheets.Sheet {
for _, rel := range rels.Relationships {
if rel.ID == v.ID {
sheetXMLPath := f.getWorksheetPath(rel.Target)
if _, ok := f.Pkg.Load(sheetXMLPath); ok {
maps[v.Name] = sheetXMLPath
}
if _, ok := f.tempFiles.Load(sheetXMLPath); ok {
maps[v.Name] = sheetXMLPath
}
}
}
}
return maps, nil
}
// getSheetXMLPath provides a function to get XML file path by given sheet
// name.
func (f *File) getSheetXMLPath(sheet string) (string, bool) {
var (
name string
ok bool
)
for sheetName, filePath := range f.sheetMap {
if strings.EqualFold(sheetName, sheet) {
name, ok = filePath, true
break
}
}
return name, ok
}
// SetSheetBackground provides a function to set background picture by given
// worksheet name and file path. Supported image types: BMP, EMF, EMZ, GIF, ICO,
// JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
func (f *File) SetSheetBackground(sheet, picture string) error {
var err error
// Check picture exists first.
if _, err = os.Stat(picture); os.IsNotExist(err) {
return err
}
file, _ := os.ReadFile(filepath.Clean(picture))
return f.setSheetBackground(sheet, path.Ext(picture), file)
}
// SetSheetBackgroundFromBytes provides a function to set background picture by
// given worksheet name, extension name and image data. Supported image types:
// BMP, EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
func (f *File) SetSheetBackgroundFromBytes(sheet, extension string, picture []byte) error {
if len(picture) == 0 {
return ErrParameterInvalid
}
return f.setSheetBackground(sheet, extension, picture)
}
// setSheetBackground provides a function to set background picture by given
// worksheet name, file name extension and image data.
func (f *File) setSheetBackground(sheet, extension string, file []byte) error {
imageType, ok := supportedImageTypes[strings.ToLower(extension)]
if !ok {
return ErrImgExt
}
name := f.addMedia(file, imageType)
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
if err := f.addSheetPicture(sheet, rID); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
return f.setContentTypePartImageExtensions()
}
// DeleteSheet provides a function to delete worksheet in a workbook by given
// worksheet name. Use this method with caution, which will affect changes in
// references such as formulas, charts, and so on. If there is any referenced
// value of the deleted worksheet, it will cause a file error when you open
// it. This function will be invalid when only one worksheet is left.
func (f *File) DeleteSheet(sheet string) error {
if err := checkSheetName(sheet); err != nil {
return err
}
if idx, _ := f.GetSheetIndex(sheet); f.SheetCount == 1 || idx == -1 {
return nil
}
f.clearCalcCache()
wb, _ := f.workbookReader()
wbRels, _ := f.relsReader(f.getWorkbookRelsPath())
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
deleteLocalSheetID, _ := f.GetSheetIndex(sheet)
deleteAndAdjustDefinedNames(wb, deleteLocalSheetID)
for idx, v := range wb.Sheets.Sheet {
if !strings.EqualFold(v.Name, sheet) {
continue
}
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...)
var sheetXML, rels string
if wbRels != nil {
for _, rel := range wbRels.Relationships {
if rel.ID == v.ID {
sheetXML = f.getWorksheetPath(rel.Target)
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
rels = "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
}
}
}
target := f.deleteSheetFromWorkbookRels(v.ID)
_ = f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, target)
_ = f.deleteCalcChain(f.getSheetID(sheet), "")
delete(f.sheetMap, v.Name)
f.Pkg.Delete(sheetXML)
f.Pkg.Delete(rels)
f.Relationships.Delete(rels)
f.Sheet.Delete(sheetXML)
f.xmlAttr.Delete(sheetXML)
f.SheetCount--
}
index, err := f.GetSheetIndex(activeSheetName)
f.SetActiveSheet(index)
return err
}
// MoveSheet moves a sheet to a specified position in the workbook. The function
// moves the source sheet before the target sheet. After moving, other sheets
// will be shifted to the left or right. If the sheet is already at the target
// position, the function will not perform any action. Not that this function
// will be ungroup all sheets after moving. For example, move Sheet2 before
// Sheet1:
//
// err := f.MoveSheet("Sheet2", "Sheet1")
func (f *File) MoveSheet(source, target string) error {
if strings.EqualFold(source, target) {
return nil
}
wb, err := f.workbookReader()
if err != nil {
return err
}
sourceIdx, err := f.GetSheetIndex(source)
if err != nil {
return err
}
targetIdx, err := f.GetSheetIndex(target)
if err != nil {
return err
}
if sourceIdx < 0 {
return ErrSheetNotExist{source}
}
if targetIdx < 0 {
return ErrSheetNotExist{target}
}
_ = f.UngroupSheets()
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
sourceSheet := wb.Sheets.Sheet[sourceIdx]
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...)
if targetIdx > sourceIdx {
targetIdx--
}
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...)...)
activeSheetIdx, _ := f.GetSheetIndex(activeSheetName)
f.SetActiveSheet(activeSheetIdx)
return err
}
// deleteAndAdjustDefinedNames delete and adjust defined name in the workbook
// by given worksheet ID.
func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) {
if wb == nil || wb.DefinedNames == nil {
return
}
for idx := 0; idx < len(wb.DefinedNames.DefinedName); idx++ {
dn := wb.DefinedNames.DefinedName[idx]
if dn.LocalSheetID != nil {
localSheetID := *dn.LocalSheetID
if localSheetID == deleteLocalSheetID {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
idx--
} else if localSheetID > deleteLocalSheetID {
wb.DefinedNames.DefinedName[idx].LocalSheetID = intPtr(*dn.LocalSheetID - 1)
}
}
}
}
// deleteSheetFromWorkbookRels provides a function to remove worksheet
// relationships by given relationships ID in the file workbook.xml.rels.
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
rels, _ := f.relsReader(f.getWorkbookRelsPath())
rels.mu.Lock()
defer rels.mu.Unlock()
for k, v := range rels.Relationships {
if v.ID == rID {
rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...)
return v.Target
}
}
return ""
}
// deleteSheetRelationships provides a function to delete relationships in
// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
// relationship index.
func (f *File) deleteSheetRelationships(sheet, rID string) {
name, ok := f.getSheetXMLPath(sheet)
if !ok {
name = strings.ToLower(sheet) + ".xml"
}
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels, _ := f.relsReader(rels)
if sheetRels == nil {
sheetRels = &xlsxRelationships{}
}
sheetRels.mu.Lock()
defer sheetRels.mu.Unlock()
for k, v := range sheetRels.Relationships {
if v.ID == rID {
sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...)
}
}
f.Relationships.Store(rels, sheetRels)
}
// getSheetRelationshipsTargetByID provides a function to get Target attribute
// value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
// relationship index.
func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
name, ok := f.getSheetXMLPath(sheet)
if !ok {
name = strings.ToLower(sheet) + ".xml"
}
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels, _ := f.relsReader(rels)
if sheetRels == nil {
sheetRels = &xlsxRelationships{}
}
sheetRels.mu.Lock()
defer sheetRels.mu.Unlock()
for _, v := range sheetRels.Relationships {
if v.ID == rID {
return v.Target
}
}
return ""
}
// CopySheet provides a function to duplicate a worksheet by gave source and
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
// // Sheet1 already exists...
// index, err := f.NewSheet("Sheet2")
// if err != nil {
// fmt.Println(err)
// return
// }
// err = f.CopySheet(0, index)
func (f *File) CopySheet(from, to int) error {
if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
return ErrSheetIdx
}
return f.copySheet(from, to)
}
// copySheet provides a function to duplicate a worksheet by gave source and
// target worksheet name.
func (f *File) copySheet(from, to int) error {
fromSheet := f.GetSheetName(from)
sheet, err := f.workSheetReader(fromSheet)
if err != nil {
return err
}
f.clearCalcCache()
worksheet := &xlsxWorksheet{}
err = deepcopy.Copy(worksheet, sheet)
toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to)))
sheetXMLPath := "xl/worksheets/sheet" + toSheetID + ".xml"
if len(worksheet.SheetViews.SheetView) > 0 {
worksheet.SheetViews.SheetView[0].TabSelected = false
}
worksheet.Drawing = nil
worksheet.TableParts = nil
worksheet.PageSetUp = nil
f.Sheet.Store(sheetXMLPath, worksheet)
toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels"
fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels"
if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil {
f.Pkg.Store(toRels, rels.([]byte))
}
fromSheetXMLPath, _ := f.getSheetXMLPath(fromSheet)
fromSheetAttr, _ := f.xmlAttr.Load(fromSheetXMLPath)
f.xmlAttr.Store(sheetXMLPath, fromSheetAttr)
return err
}
// getSheetState returns sheet visible enumeration by given hidden status.
func getSheetState(visible bool, veryHidden []bool) string {
state := "hidden"
if !visible && len(veryHidden) > 0 && veryHidden[0] {
state = "veryHidden"
}
return state
}
// SetSheetVisible provides a function to set worksheet visible by given
// worksheet name. A workbook must contain at least one visible worksheet. If
// the given worksheet has been activated, this setting will be invalidated.
// The third optional veryHidden parameter only works when visible was false.
//
// For example, hide Sheet1:
//
// err := f.SetSheetVisible("Sheet1", false)
func (f *File) SetSheetVisible(sheet string, visible bool, veryHidden ...bool) error {
if err := checkSheetName(sheet); err != nil {
return err
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if visible {
for k, v := range wb.Sheets.Sheet {
if strings.EqualFold(v.Name, sheet) {
wb.Sheets.Sheet[k].State = ""
}
}
return err
}
count, state := 0, getSheetState(visible, veryHidden)
for _, v := range wb.Sheets.Sheet {
if v.State != state {
count++
}
}
for k, v := range wb.Sheets.Sheet {
ws, err := f.workSheetReader(v.Name)
if err != nil {
return err
}
tabSelected := false
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
}
}
if len(ws.SheetViews.SheetView) > 0 {
tabSelected = ws.SheetViews.SheetView[0].TabSelected
}
if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected {
wb.Sheets.Sheet[k].State = state
}
}
return err
}
// setPanes set create freeze panes and split panes by given options.
func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
if panes == nil {
return ErrParameterInvalid
}
p := &xlsxPane{
ActivePane: panes.ActivePane,
TopLeftCell: panes.TopLeftCell,
XSplit: float64(panes.XSplit),
YSplit: float64(panes.YSplit),
}
if panes.Freeze {
p.State = "frozen"
}
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
}
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p
if !(panes.Freeze) && !(panes.Split) {
if len(ws.SheetViews.SheetView) > 0 {
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil
}
}
var s []*xlsxSelection
for _, p := range panes.Selection {
s = append(s, &xlsxSelection{
ActiveCell: p.ActiveCell,
Pane: p.Pane,
SQRef: p.SQRef,
})
}
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s
return nil
}
// SetPanes provides a function to create and remove freeze panes and split panes
// by given worksheet name and panes options.
//
// ActivePane defines the pane that is active. The possible values for this
// attribute are defined in the following table:
//
// Enumeration Value | Description
// ---------------------------------+-------------------------------------------------------------
// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal
// | splits are applied.
// |
// | This value is also used when only a horizontal split has
// | been applied, dividing the pane into upper and lower
// | regions. In that case, this value specifies the bottom
// | pane.
// |
// bottomRight (Bottom Right Pane) | Bottom right pane, when both vertical and horizontal
// | splits are applied.
// |
// topLeft (Top Left Pane) | Top left pane, when both vertical and horizontal splits
// | are applied.
// |
// | This value is also used when only a horizontal split has
// | been applied, dividing the pane into upper and lower
// | regions. In that case, this value specifies the top pane.
// |
// | This value is also used when only a vertical split has
// | been applied, dividing the pane into right and left
// | regions. In that case, this value specifies the left pane
// |
// topRight (Top Right Pane) | Top right pane, when both vertical and horizontal
// | splits are applied.
// |
// | This value is also used when only a vertical split has
// | been applied, dividing the pane into right and left
// | regions. In that case, this value specifies the right
// | pane.
//
// Pane state type is restricted to the values supported currently listed in the following table:
//
// Enumeration Value | Description
// ---------------------------------+-------------------------------------------------------------
// frozen (Frozen) | Panes are frozen, but were not split being frozen. In
// | this state, when the panes are unfrozen again, a single
// | pane results, with no split.
// |
// | In this state, the split bars are not adjustable.
// |
// split (Split) | Panes are split, but not frozen. In this state, the split
// | bars are adjustable by the user.
//
// XSplit (Horizontal Split Position): Horizontal position of the split, in
// 1/20th of a point; 0 (zero) if none. If the pane is frozen, this value
// indicates the number of columns visible in the top pane.
//
// YSplit (Vertical Split Position): Vertical position of the split, in 1/20th
// of a point; 0 (zero) if none. If the pane is frozen, this value indicates the
// number of rows visible in the left pane. The possible values for this
// attribute are defined by the W3C XML Schema double datatype.
//
// TopLeftCell: Location of the top left visible cell in the bottom right pane
// (when in Left-To-Right mode).
//
// SQRef (Sequence of References): Range of the selection. Can be non-contiguous
// set of ranges.
//
// An example of how to freeze column A in the Sheet1 and set the active cell on
// Sheet1!K16:
//
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: true,
// Split: false,
// XSplit: 1,
// YSplit: 0,
// TopLeftCell: "B1",
// ActivePane: "topRight",
// Selection: []excelize.Selection{
// {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
// },
// })
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
// ranges on Sheet1!A11:XFD11:
//
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: true,
// Split: false,
// XSplit: 0,
// YSplit: 9,
// TopLeftCell: "A34",
// ActivePane: "bottomLeft",
// Selection: []excelize.Selection{
// {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
// },
// })
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
// err := f.SetPanes("Sheet1", &excelize.Panes{
// Freeze: false,
// Split: true,
// XSplit: 3270,
// YSplit: 1800,
// TopLeftCell: "N57",
// ActivePane: "bottomLeft",
// Selection: []excelize.Selection{
// {SQRef: "I36", ActiveCell: "I36"},
// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
// {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
// {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"},
// },
// })
//
// An example of how to unfreeze and remove all panes on Sheet1:
//
// err := f.SetPanes("Sheet1", &excelize.Panes{Freeze: false, Split: false})
func (f *File) SetPanes(sheet string, panes *Panes) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
return ws.setPanes(panes)
}
// getPanes returns freeze panes, split panes, and views of the worksheet.
func (ws *xlsxWorksheet) getPanes() Panes {
var (
panes Panes
section []Selection
)
if ws.SheetViews == nil || len(ws.SheetViews.SheetView) < 1 {
return panes
}
sw := ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1]
for _, s := range sw.Selection {
if s != nil {
section = append(section, Selection{
SQRef: s.SQRef,
ActiveCell: s.ActiveCell,
Pane: s.Pane,
})
}
}
panes.Selection = section
if sw.Pane == nil {
return panes
}
panes.ActivePane = sw.Pane.ActivePane
if sw.Pane.State == "frozen" {
panes.Freeze = true
}
panes.TopLeftCell = sw.Pane.TopLeftCell
panes.XSplit = int(sw.Pane.XSplit)
panes.YSplit = int(sw.Pane.YSplit)
return panes
}
// GetPanes provides a function to get freeze panes, split panes, and worksheet
// views by given worksheet name.
func (f *File) GetPanes(sheet string) (Panes, error) {
var panes Panes
ws, err := f.workSheetReader(sheet)
if err != nil {
return panes, err
}
return ws.getPanes(), err
}
// GetSheetVisible provides a function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1:
//
// visible, err := f.GetSheetVisible("Sheet1")
func (f *File) GetSheetVisible(sheet string) (bool, error) {
var visible bool
if err := checkSheetName(sheet); err != nil {
return visible, err
}
wb, _ := f.workbookReader()
for k, v := range wb.Sheets.Sheet {
if strings.EqualFold(v.Name, sheet) {
if wb.Sheets.Sheet[k].State == "" || wb.Sheets.Sheet[k].State == "visible" {
visible = true
}
}
}
return visible, nil
}
// SearchSheet provides a function to get cell reference by given worksheet name,
// cell value, and regular expression. The function doesn't support searching
// on the calculated result, formatted numbers and conditional lookup
// currently. If it is a merged cell, it will return the cell reference of the
// upper left cell of the merged range reference.
//
// An example of search the cell reference of the value of "100" on Sheet1:
//
// result, err := f.SearchSheet("Sheet1", "100")
//
// An example of search the cell reference where the numerical value in the range
// of "0-9" of Sheet1 is described:
//
// result, err := f.SearchSheet("Sheet1", "[0-9]", true)
func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) {
var (
regSearch bool
result []string
)
if err := checkSheetName(sheet); err != nil {
return result, err
}
for _, r := range reg {
regSearch = r
}
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return result, ErrSheetNotExist{sheet}
}
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
// Flush data
output, _ := xml.Marshal(ws.(*xlsxWorksheet))
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
return f.searchSheet(name, value, regSearch)
}
// searchSheet provides a function to get cell reference by given worksheet
// name, cell value, and regular expression.
func (f *File) searchSheet(name, value string, regSearch bool) (result []string, err error) {
var (
cellName, inElement string
cellCol, row int
sst *xlsxSST
)
if sst, err = f.sharedStringsReader(); err != nil {
return
}
regex := regexp.MustCompile(value)
decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name)))
for {
var token xml.Token
token, err = decoder.Token()
if err != nil || token == nil {
if err == io.EOF {
err = nil
}
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
inElement = xmlElement.Name.Local
if inElement == "row" {
row, err = attrValToInt("r", xmlElement.Attr)
if err != nil {
return
}
}
if inElement == "c" {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(f, sst, false)
if regSearch {
if !regex.MatchString(val) {
continue
}
} else {
if val != value {
continue
}
}
cellCol, _, err = CellNameToCoordinates(colCell.R)
if err != nil {
return result, err
}
cellName, err = CoordinatesToCellName(cellCol, row)
if err != nil {
return result, err
}
result = append(result, cellName)
}
default:
}
}
return
}
// attrValToInt provides a function to convert the local names to an integer
// by given XML attributes and specified names.
func attrValToInt(name string, attrs []xml.Attr) (val int, err error) {
for _, attr := range attrs {
if attr.Name.Local == name {
val, err = strconv.Atoi(attr.Value)
if err != nil {
return
}
}
}
return
}
// attrValToFloat provides a function to convert the local names to a float64
// by given XML attributes and specified names.
func attrValToFloat(name string, attrs []xml.Attr) (val float64, err error) {
for _, attr := range attrs {
if attr.Name.Local == name {
val, err = strconv.ParseFloat(attr.Value, 64)
if err != nil {
return
}
}
}
return
}
// attrValToBool provides a function to convert the local names to a boolean
// by given XML attributes and specified names.
func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) {
for _, attr := range attrs {
if attr.Name.Local == name {
val, err = strconv.ParseBool(attr.Value)
if err != nil {
return
}
}
}
return
}
// SetHeaderFooter provides a function to set headers and footers by given
// worksheet name and the control characters.
//
// Headers and footers are specified using the following settings fields:
//
// Fields | Description
// ------------------+-----------------------------------------------------------
// AlignWithMargins | Align header footer margins with page margins
// DifferentFirst | Different first-page header and footer indicator
// DifferentOddEven | Different odd and even page headers and footers indicator
// ScaleWithDoc | Scale header and footer with document scaling
// OddFooter | Odd Page Footer, or primary Page Footer if 'DifferentOddEven' is 'false'
// OddHeader | Odd Header, or primary Page Header if 'DifferentOddEven' is 'false'
// EvenFooter | Even Page Footer
// EvenHeader | Even Page Header
// FirstFooter | First Page Footer
// FirstHeader | First Page Header
//
// The following formatting codes can be used in 6 string type fields:
// OddHeader, OddFooter, EvenHeader, EvenFooter, FirstFooter, FirstHeader
//
// Formatting Code | Description
// ------------------------+-------------------------------------------------------------------------
// && | The character "&"
// |
// &font-size | Size of the text font, where font-size is a decimal font size in points
// |
// &"font name,font type" | A text font-name string, font name, and a text font-type string,
// | font type
// |
// &"-,Regular" | Regular text format. Toggles bold and italic modes to off
// |
// &A | Current worksheet's tab name
// |
// &B or &"-,Bold" | Bold text format, from off to on, or vice versa. The default mode is off
// |
// &D | Current date
// |
// &C | Center section
// |
// &E | Double-underline text format
// |
// &F | Current workbook's file name
// |
// &G | Drawing object as background (Use AddHeaderFooterImage)
// |
// &H | Shadow text format
// |
// &I or &"-,Italic" | Italic text format
// |
// &K | Text font color
// |
// | An RGB Color is specified as RRGGBB
// |
// | A Theme Color is specified as TTSNNN where TT is the theme color Id,
// | S is either "+" or "-" of the tint/shade value, and NNN is the
// | tint/shade value
// |
// &L | Left section
// |
// &N | Total number of pages
// |
// &O | Outline text format
// |
// &P[[+|-]n] | Without the optional suffix, the current page number in decimal
// |
// &R | Right section
// |
// &S | Strike through text format
// |
// &T | Current time
// |
// &U | Single-underline text format. If double-underline mode is on, the next
// | occurrence in a section specifier toggles double-underline mode to off;
// | otherwise, it toggles single-underline mode, from off to on, or vice
// | versa. The default mode is off
// |
// &X | Superscript text format
// |
// &Y | Subscript text format
// |
// &Z | Current workbook's file path
//
// For example:
//
// err := f.SetHeaderFooter("Sheet1", &excelize.HeaderFooterOptions{
// DifferentFirst: true,
// DifferentOddEven: true,
// OddHeader: "&R&P",
// OddFooter: "&C&F",
// EvenHeader: "&L&P",
// EvenFooter: "&L&D&R&T",
// FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`,
// })
//
// This example shows:
//
// - The first page has its own header and footer
//
// - Odd and even-numbered pages have different headers and footers
//
// - Current page number in the right section of odd-page headers
//
// - Current workbook's file name in the center section of odd-page footers
//
// - Current page number in the left section of even-page headers
//
// - Current date in the left section and the current time in the right section
// of even-page footers
//
// - The text "Center Bold Header" on the first line of the center section of
// the first page, and the date on the second line of the center section of
// that same page
//
// - No footer on the first page
func (f *File) SetHeaderFooter(sheet string, opts *HeaderFooterOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if opts == nil {
ws.HeaderFooter = nil
return err
}
v := reflect.ValueOf(*opts)
// Check 6 string type fields: OddHeader, OddFooter, EvenHeader, EvenFooter,
// FirstFooter, FirstHeader
for i := 4; i < v.NumField()-1; i++ {
if countUTF16String(v.Field(i).String()) > MaxFieldLength {
return newFieldLengthError(v.Type().Field(i).Name)
}
}
ws.HeaderFooter = &xlsxHeaderFooter{
AlignWithMargins: opts.AlignWithMargins,
DifferentFirst: opts.DifferentFirst,
DifferentOddEven: opts.DifferentOddEven,
ScaleWithDoc: opts.ScaleWithDoc,
OddHeader: opts.OddHeader,
OddFooter: opts.OddFooter,
EvenHeader: opts.EvenHeader,
EvenFooter: opts.EvenFooter,
FirstFooter: opts.FirstFooter,
FirstHeader: opts.FirstHeader,
}
return err
}
// GetHeaderFooter provides a function to get worksheet header and footer by
// given worksheet name.
func (f *File) GetHeaderFooter(sheet string) (*HeaderFooterOptions, error) {
var opts *HeaderFooterOptions
ws, err := f.workSheetReader(sheet)
if err != nil {
return opts, err
}
if ws.HeaderFooter == nil {
return opts, err
}
opts = &HeaderFooterOptions{
AlignWithMargins: ws.HeaderFooter.AlignWithMargins,
DifferentFirst: ws.HeaderFooter.DifferentFirst,
DifferentOddEven: ws.HeaderFooter.DifferentOddEven,
ScaleWithDoc: ws.HeaderFooter.ScaleWithDoc,
OddHeader: ws.HeaderFooter.OddHeader,
OddFooter: ws.HeaderFooter.OddFooter,
EvenHeader: ws.HeaderFooter.EvenHeader,
EvenFooter: ws.HeaderFooter.EvenFooter,
FirstHeader: ws.HeaderFooter.FirstHeader,
FirstFooter: ws.HeaderFooter.FirstFooter,
}
return opts, err
}
// ProtectSheet provides a function to prevent other users from accidentally or
// deliberately changing, moving, or deleting data in a worksheet. The
// optional field AlgorithmName specified hash algorithm, support XOR, MD4,
// MD5, SHA-1, SHA2-56, SHA-384, and SHA-512 currently, if no hash algorithm
// specified, will be using the XOR algorithm as default. For example, protect
// Sheet1 with protection settings:
//
// err := f.ProtectSheet("Sheet1", &excelize.SheetProtectionOptions{
// AlgorithmName: "SHA-512",
// Password: "password",
// SelectLockedCells: true,
// SelectUnlockedCells: true,
// EditScenarios: true,
// })
func (f *File) ProtectSheet(sheet string, opts *SheetProtectionOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if opts == nil {
return ErrParameterInvalid
}
ws.SheetProtection = &xlsxSheetProtection{
AutoFilter: !opts.AutoFilter,
DeleteColumns: !opts.DeleteColumns,
DeleteRows: !opts.DeleteRows,
FormatCells: !opts.FormatCells,
FormatColumns: !opts.FormatColumns,
FormatRows: !opts.FormatRows,
InsertColumns: !opts.InsertColumns,
InsertHyperlinks: !opts.InsertHyperlinks,
InsertRows: !opts.InsertRows,
Objects: !opts.EditObjects,
PivotTables: !opts.PivotTables,
Scenarios: !opts.EditScenarios,
SelectLockedCells: !opts.SelectLockedCells,
SelectUnlockedCells: !opts.SelectUnlockedCells,
Sheet: true,
Sort: !opts.Sort,
}
if opts.Password != "" {
if opts.AlgorithmName == "" {
ws.SheetProtection.Password = genSheetPasswd(opts.Password)
return err
}
hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(sheetProtectionSpinCount))
if err != nil {
return err
}
ws.SheetProtection.Password = ""
ws.SheetProtection.AlgorithmName = opts.AlgorithmName
ws.SheetProtection.SaltValue = saltValue
ws.SheetProtection.HashValue = hashValue
ws.SheetProtection.SpinCount = int(sheetProtectionSpinCount)
}
return err
}
// UnprotectSheet provides a function to remove protection for a sheet,
// specified the second optional password parameter to remove sheet
// protection with password verification.
func (f *File) UnprotectSheet(sheet string, password ...string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
// password verification
if len(password) > 0 {
if ws.SheetProtection == nil {
return ErrUnprotectSheet
}
if ws.SheetProtection.AlgorithmName == "" && ws.SheetProtection.Password != genSheetPasswd(password[0]) {
return ErrUnprotectSheetPassword
}
if ws.SheetProtection.AlgorithmName != "" {
// check with given salt value
hashValue, _, err := genISOPasswdHash(password[0], ws.SheetProtection.AlgorithmName, ws.SheetProtection.SaltValue, ws.SheetProtection.SpinCount)
if err != nil {
return err
}
if ws.SheetProtection.HashValue != hashValue {
return ErrUnprotectSheetPassword
}
}
}
ws.SheetProtection = nil
return err
}
// GetSheetProtection provides a function to get worksheet protection settings
// by given worksheet name. Note that the password in the returned will always
// be empty.
func (f *File) GetSheetProtection(sheet string) (SheetProtectionOptions, error) {
var opts SheetProtectionOptions
ws, err := f.workSheetReader(sheet)
if err != nil {
return opts, err
}
if ws.SheetProtection != nil {
opts.AlgorithmName = ws.SheetProtection.AlgorithmName
opts.AutoFilter = !ws.SheetProtection.AutoFilter
opts.DeleteColumns = !ws.SheetProtection.DeleteColumns
opts.DeleteRows = !ws.SheetProtection.DeleteRows
opts.EditObjects = !ws.SheetProtection.Objects
opts.EditScenarios = !ws.SheetProtection.Scenarios
opts.FormatCells = !ws.SheetProtection.FormatCells
opts.FormatColumns = !ws.SheetProtection.FormatColumns
opts.FormatRows = !ws.SheetProtection.FormatRows
opts.InsertColumns = !ws.SheetProtection.InsertColumns
opts.InsertHyperlinks = !ws.SheetProtection.InsertHyperlinks
opts.InsertRows = !ws.SheetProtection.InsertRows
opts.PivotTables = !ws.SheetProtection.PivotTables
opts.SelectLockedCells = !ws.SheetProtection.SelectLockedCells
opts.SelectUnlockedCells = !ws.SheetProtection.SelectUnlockedCells
opts.Sort = !ws.SheetProtection.Sort
}
return opts, err
}
// checkSheetName check whether there are illegal characters in the sheet name.
// 1. Confirm that the sheet name is not empty
// 2. Make sure to enter a name with no more than 31 characters
// 3. Make sure the first or last character of the name cannot be a single quote
// 4. Verify that the following characters are not included in the name :\/?*[]
func checkSheetName(name string) error {
if name == "" {
return ErrSheetNameBlank
}
if countUTF16String(name) > MaxSheetNameLength {
return ErrSheetNameLength
}
if strings.HasPrefix(name, "'") || strings.HasSuffix(name, "'") {
return ErrSheetNameSingleQuote
}
if strings.ContainsAny(name, ":\\/?*[]") {
return ErrSheetNameInvalid
}
return nil
}
// SetPageLayout provides a function to sets worksheet page layout.
//
// The following shows the paper size sorted by excelize index number:
//
// Index | Paper Size
// -------+-----------------------------------------------
// 1 | Letter paper (8.5 in. by 11 in.)
// 2 | Letter small paper (8.5 in. by 11 in.)
// 3 | Tabloid paper (11 in. by 17 in.)
// 4 | Ledger paper (17 in. by 11 in.)
// 5 | Legal paper (8.5 in. by 14 in.)
// 6 | Statement paper (5.5 in. by 8.5 in.)
// 7 | Executive paper (7.25 in. by 10.5 in.)
// 8 | A3 paper (297 mm by 420 mm)
// 9 | A4 paper (210 mm by 297 mm)
// 10 | A4 small paper (210 mm by 297 mm)
// 11 | A5 paper (148 mm by 210 mm)
// 12 | B4 paper (250 mm by 353 mm)
// 13 | B5 paper (176 mm by 250 mm)
// 14 | Folio paper (8.5 in. by 13 in.)
// 15 | Quarto paper (215 mm by 275 mm)
// 16 | Standard paper (10 in. by 14 in.)
// 17 | Standard paper (11 in. by 17 in.)
// 18 | Note paper (8.5 in. by 11 in.)
// 19 | #9 envelope (3.875 in. by 8.875 in.)
// 20 | #10 envelope (4.125 in. by 9.5 in.)
// 21 | #11 envelope (4.5 in. by 10.375 in.)
// 22 | #12 envelope (4.75 in. by 11 in.)
// 23 | #14 envelope (5 in. by 11.5 in.)
// 24 | C paper (17 in. by 22 in.)
// 25 | D paper (22 in. by 34 in.)
// 26 | E paper (34 in. by 44 in.)
// 27 | DL envelope (110 mm by 220 mm)
// 28 | C5 envelope (162 mm by 229 mm)
// 29 | C3 envelope (324 mm by 458 mm)
// 30 | C4 envelope (229 mm by 324 mm)
// 31 | C6 envelope (114 mm by 162 mm)
// 32 | C65 envelope (114 mm by 229 mm)
// 33 | B4 envelope (250 mm by 353 mm)
// 34 | B5 envelope (176 mm by 250 mm)
// 35 | B6 envelope (176 mm by 125 mm)
// 36 | Italy envelope (110 mm by 230 mm)
// 37 | Monarch envelope (3.875 in. by 7.5 in.).
// 38 | 6 3/4 envelope (3.625 in. by 6.5 in.)
// 39 | US standard fanfold (14.875 in. by 11 in.)
// 40 | German standard fanfold (8.5 in. by 12 in.)
// 41 | German legal fanfold (8.5 in. by 13 in.)
// 42 | ISO B4 (250 mm by 353 mm)
// 43 | Japanese postcard (100 mm by 148 mm)
// 44 | Standard paper (9 in. by 11 in.)
// 45 | Standard paper (10 in. by 11 in.)
// 46 | Standard paper (15 in. by 11 in.)
// 47 | Invite envelope (220 mm by 220 mm)
// 50 | Letter extra paper (9.275 in. by 12 in.)
// 51 | Legal extra paper (9.275 in. by 15 in.)
// 52 | Tabloid extra paper (11.69 in. by 18 in.)
// 53 | A4 extra paper (236 mm by 322 mm)
// 54 | Letter transverse paper (8.275 in. by 11 in.)
// 55 | A4 transverse paper (210 mm by 297 mm)
// 56 | Letter extra transverse paper (9.275 in. by 12 in.)
// 57 | SuperA/SuperA/A4 paper (227 mm by 356 mm)
// 58 | SuperB/SuperB/A3 paper (305 mm by 487 mm)
// 59 | Letter plus paper (8.5 in. by 12.69 in.)
// 60 | A4 plus paper (210 mm by 330 mm)
// 61 | A5 transverse paper (148 mm by 210 mm)
// 62 | JIS B5 transverse paper (182 mm by 257 mm)
// 63 | A3 extra paper (322 mm by 445 mm)
// 64 | A5 extra paper (174 mm by 235 mm)
// 65 | ISO B5 extra paper (201 mm by 276 mm)
// 66 | A2 paper (420 mm by 594 mm)
// 67 | A3 transverse paper (297 mm by 420 mm)
// 68 | A3 extra transverse paper (322 mm by 445 mm)
// 69 | Japanese Double Postcard (200 mm x 148 mm)
// 70 | A6 (105 mm x 148 mm)
// 71 | Japanese Envelope Kaku #2
// 72 | Japanese Envelope Kaku #3
// 73 | Japanese Envelope Chou #3
// 74 | Japanese Envelope Chou #4
// 75 | Letter Rotated (11in x 8 1/2 11 in)
// 76 | A3 Rotated (420 mm x 297 mm)
// 77 | A4 Rotated (297 mm x 210 mm)
// 78 | A5 Rotated (210 mm x 148 mm)
// 79 | B4 (JIS) Rotated (364 mm x 257 mm)
// 80 | B5 (JIS) Rotated (257 mm x 182 mm)
// 81 | Japanese Postcard Rotated (148 mm x 100 mm)
// 82 | Double Japanese Postcard Rotated (148 mm x 200 mm)
// 83 | A6 Rotated (148 mm x 105 mm)
// 84 | Japanese Envelope Kaku #2 Rotated
// 85 | Japanese Envelope Kaku #3 Rotated
// 86 | Japanese Envelope Chou #3 Rotated
// 87 | Japanese Envelope Chou #4 Rotated
// 88 | B6 (JIS) (128 mm x 182 mm)
// 89 | B6 (JIS) Rotated (182 mm x 128 mm)
// 90 | (12 in x 11 in)
// 91 | Japanese Envelope You #4
// 92 | Japanese Envelope You #4 Rotated
// 93 | PRC 16K (146 mm x 215 mm)
// 94 | PRC 32K (97 mm x 151 mm)
// 95 | PRC 32K(Big) (97 mm x 151 mm)
// 96 | PRC Envelope #1 (102 mm x 165 mm)
// 97 | PRC Envelope #2 (102 mm x 176 mm)
// 98 | PRC Envelope #3 (125 mm x 176 mm)
// 99 | PRC Envelope #4 (110 mm x 208 mm)
// 100 | PRC Envelope #5 (110 mm x 220 mm)
// 101 | PRC Envelope #6 (120 mm x 230 mm)
// 102 | PRC Envelope #7 (160 mm x 230 mm)
// 103 | PRC Envelope #8 (120 mm x 309 mm)
// 104 | PRC Envelope #9 (229 mm x 324 mm)
// 105 | PRC Envelope #10 (324 mm x 458 mm)
// 106 | PRC 16K Rotated
// 107 | PRC 32K Rotated
// 108 | PRC 32K(Big) Rotated
// 109 | PRC Envelope #1 Rotated (165 mm x 102 mm)
// 110 | PRC Envelope #2 Rotated (176 mm x 102 mm)
// 111 | PRC Envelope #3 Rotated (176 mm x 125 mm)
// 112 | PRC Envelope #4 Rotated (208 mm x 110 mm)
// 113 | PRC Envelope #5 Rotated (220 mm x 110 mm)
// 114 | PRC Envelope #6 Rotated (230 mm x 120 mm)
// 115 | PRC Envelope #7 Rotated (230 mm x 160 mm)
// 116 | PRC Envelope #8 Rotated (309 mm x 120 mm)
// 117 | PRC Envelope #9 Rotated (324 mm x 229 mm)
// 118 | PRC Envelope #10 Rotated (458 mm x 324 mm)
func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if opts == nil {
return err
}
return ws.setPageSetUp(opts)
}
// newPageSetUp initialize page setup settings for the worksheet if which not
// exist.
func (ws *xlsxWorksheet) newPageSetUp() {
if ws.PageSetUp == nil {
ws.PageSetUp = new(xlsxPageSetUp)
}
}
// setPageSetUp set page setup settings for the worksheet by given options.
func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) error {
if opts.Size != nil {
ws.newPageSetUp()
ws.PageSetUp.PaperSize = opts.Size
}
if opts.Orientation != nil {
if inStrSlice(supportedPageOrientation, *opts.Orientation, true) == -1 {
return newInvalidOptionalValue("Orientation", *opts.Orientation, supportedPageOrientation)
}
ws.newPageSetUp()
ws.PageSetUp.Orientation = *opts.Orientation
}
if opts.FirstPageNumber != nil && *opts.FirstPageNumber > 0 {
ws.newPageSetUp()
ws.PageSetUp.FirstPageNumber = strconv.Itoa(int(*opts.FirstPageNumber))
ws.PageSetUp.UseFirstPageNumber = true
}
if opts.AdjustTo != nil {
if *opts.AdjustTo < 10 || 400 < *opts.AdjustTo {
return ErrPageSetupAdjustTo
}
ws.newPageSetUp()
ws.PageSetUp.Scale = int(*opts.AdjustTo)
}
if opts.FitToHeight != nil {
ws.newPageSetUp()
ws.PageSetUp.FitToHeight = opts.FitToHeight
}
if opts.FitToWidth != nil {
ws.newPageSetUp()
ws.PageSetUp.FitToWidth = opts.FitToWidth
}
if opts.BlackAndWhite != nil {
ws.newPageSetUp()
ws.PageSetUp.BlackAndWhite = *opts.BlackAndWhite
}
if opts.PageOrder != nil {
if inStrSlice(supportedPageOrder, *opts.PageOrder, true) == -1 {
return newInvalidOptionalValue("PageOrder", *opts.PageOrder, supportedPageOrder)
}
ws.newPageSetUp()
ws.PageSetUp.PageOrder = *opts.PageOrder
}
return nil
}
// GetPageLayout provides a function to gets worksheet page layout.
func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
opts := PageLayoutOptions{
Size: intPtr(0),
Orientation: stringPtr(supportedPageOrientation[0]),
FirstPageNumber: uintPtr(1),
AdjustTo: uintPtr(100),
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return opts, err
}
if ws.PageSetUp != nil {
if ws.PageSetUp.PaperSize != nil {
opts.Size = ws.PageSetUp.PaperSize
}
if ws.PageSetUp.Orientation != "" {
opts.Orientation = stringPtr(ws.PageSetUp.Orientation)
}
if num, _ := strconv.Atoi(ws.PageSetUp.FirstPageNumber); num != 0 {
opts.FirstPageNumber = uintPtr(uint(num))
}
if ws.PageSetUp.Scale >= 10 && ws.PageSetUp.Scale <= 400 {
opts.AdjustTo = uintPtr(uint(ws.PageSetUp.Scale))
}
if ws.PageSetUp.FitToHeight != nil {
opts.FitToHeight = ws.PageSetUp.FitToHeight
}
if ws.PageSetUp.FitToWidth != nil {
opts.FitToWidth = ws.PageSetUp.FitToWidth
}
opts.BlackAndWhite = boolPtr(ws.PageSetUp.BlackAndWhite)
if ws.PageSetUp.PageOrder != "" {
opts.PageOrder = stringPtr(ws.PageSetUp.PageOrder)
}
}
return opts, err
}
// SetDefinedName provides a function to set the defined names of the workbook
// or worksheet. If not specified scope, the default scope is workbook.
// For example:
//
// err := f.SetDefinedName(&excelize.DefinedName{
// Name: "Amount",
// RefersTo: "Sheet1!$A$2:$D$5",
// Comment: "defined name comment",
// })
//
// If you fill the RefersTo property with only one columns range without a
// comma, it will work as "Columns to repeat at left" only. For example:
//
// err := f.SetDefinedName(&excelize.DefinedName{
// Name: "_xlnm.Print_Titles",
// RefersTo: "Sheet1!$A:$A",
// Scope: "Sheet1",
// })
//
// If you fill the RefersTo property with only one rows range without a comma,
// it will work as "Rows to repeat at top" only. For example:
//
// err := f.SetDefinedName(&excelize.DefinedName{
// Name: "_xlnm.Print_Titles",
// RefersTo: "Sheet1!$1:$1",
// Scope: "Sheet1",
// })
//
// You can also use the function in RefersTo. For example:
//
// err := f.SetDefinedName(&excelize.DefinedName{
// Name: "CustomRange",
// RefersTo: "Sheet1!$A$2+Sheet1!$D$5",
// Scope: "Sheet1",
// })
func (f *File) SetDefinedName(definedName *DefinedName) error {
if definedName.Name == "" || definedName.RefersTo == "" {
return ErrParameterInvalid
}
if err := checkDefinedName(definedName.Name); err != nil && inStrSlice(builtInDefinedNames[:2], definedName.Name, false) == -1 {
return err
}
wb, err := f.workbookReader()
if err != nil {
return err
}
f.clearCalcCache()
d := xlsxDefinedName{
Name: definedName.Name,
Comment: definedName.Comment,
Data: definedName.RefersTo,
}
if definedName.Scope != "" {
if sheetIndex, _ := f.GetSheetIndex(definedName.Scope); sheetIndex >= 0 {
d.LocalSheetID = &sheetIndex
}
}
if wb.DefinedNames != nil {
for _, dn := range wb.DefinedNames.DefinedName {
var scope string
if dn.LocalSheetID != nil {
scope = f.GetSheetName(*dn.LocalSheetID)
}
if scope == definedName.Scope && dn.Name == definedName.Name {
return ErrDefinedNameDuplicate
}
}
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
return nil
}
wb.DefinedNames = &xlsxDefinedNames{
DefinedName: []xlsxDefinedName{d},
}
return nil
}
// DeleteDefinedName provides a function to delete the defined names of the
// workbook or worksheet. If not specified scope, the default scope is
// workbook. For example:
//
// err := f.DeleteDefinedName(&excelize.DefinedName{
// Name: "Amount",
// Scope: "Sheet2",
// })
func (f *File) DeleteDefinedName(definedName *DefinedName) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
f.clearCalcCache()
if wb.DefinedNames != nil {
for idx, dn := range wb.DefinedNames.DefinedName {
scope := "Workbook"
deleteScope := definedName.Scope
if deleteScope == "" {
deleteScope = "Workbook"
}
if dn.LocalSheetID != nil {
scope = f.GetSheetName(*dn.LocalSheetID)
}
if scope == deleteScope && dn.Name == definedName.Name {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
return err
}
}
}
return ErrDefinedNameScope
}
// GetDefinedName provides a function to get the defined names of the workbook
// or worksheet.
func (f *File) GetDefinedName() []DefinedName {
var definedNames []DefinedName
wb, _ := f.workbookReader()
if wb.DefinedNames != nil {
for _, dn := range wb.DefinedNames.DefinedName {
definedName := DefinedName{
Name: dn.Name,
Comment: dn.Comment,
RefersTo: dn.Data,
Scope: "Workbook",
}
if dn.LocalSheetID != nil && *dn.LocalSheetID >= 0 {
definedName.Scope = f.GetSheetName(*dn.LocalSheetID)
}
definedNames = append(definedNames, definedName)
}
}
return definedNames
}
// GroupSheets provides a function to group worksheets by given worksheets
// name. Group worksheets must contain an active worksheet.
func (f *File) GroupSheets(sheets []string) error {
// Check an active worksheet in group worksheets
var inActiveSheet bool
activeSheet := f.GetActiveSheetIndex()
sheetMap := f.GetSheetList()
for idx, sheetName := range sheetMap {
for _, s := range sheets {
if strings.EqualFold(s, sheetName) && idx == activeSheet {
inActiveSheet = true
}
}
}
if !inActiveSheet {
return ErrGroupSheets
}
// check worksheet exists
var wss []*xlsxWorksheet
for _, sheet := range sheets {
worksheet, err := f.workSheetReader(sheet)
if err != nil {
return err
}
wss = append(wss, worksheet)
}
for _, ws := range wss {
sheetViews := ws.SheetViews.SheetView
for idx := range sheetViews {
ws.SheetViews.SheetView[idx].TabSelected = true
}
}
return nil
}
// UngroupSheets provides a function to ungroup worksheets.
func (f *File) UngroupSheets() error {
activeSheet := f.GetActiveSheetIndex()
for index, sheet := range f.GetSheetList() {
if activeSheet == index {
continue
}
ws, _ := f.workSheetReader(sheet)
sheetViews := ws.SheetViews.SheetView
for idx := range sheetViews {
ws.SheetViews.SheetView[idx].TabSelected = false
}
}
return nil
}
// InsertPageBreak create a page break to determine where the printed page
// ends and where begins the next one by given worksheet name and cell
// reference, so the content before the page break will be printed on one page
// and after the page break on another.
func (f *File) InsertPageBreak(sheet, cell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
return ws.insertPageBreak(cell)
}
// insertPageBreak create a page break in the worksheet by specific cell
// reference.
func (ws *xlsxWorksheet) insertPageBreak(cell string) error {
var (
row, col int
err error
rowBrk, colBrk = -1, -1
)
if col, row, err = CellNameToCoordinates(cell); err != nil {
return err
}
col--
row--
if col == row && col == 0 {
return err
}
if ws.RowBreaks == nil {
ws.RowBreaks = &xlsxRowBreaks{}
}
if ws.ColBreaks == nil {
ws.ColBreaks = &xlsxColBreaks{}
}
for idx, brk := range ws.RowBreaks.Brk {
if brk.ID == row {
rowBrk = idx
}
}
for idx, brk := range ws.ColBreaks.Brk {
if brk.ID == col {
colBrk = idx
}
}
if row != 0 && rowBrk == -1 {
ws.RowBreaks.Brk = append(ws.RowBreaks.Brk, &xlsxBrk{
ID: row,
Max: MaxColumns - 1,
Man: true,
})
ws.RowBreaks.ManualBreakCount++
}
if col != 0 && colBrk == -1 {
ws.ColBreaks.Brk = append(ws.ColBreaks.Brk, &xlsxBrk{
ID: col,
Max: TotalRows - 1,
Man: true,
})
ws.ColBreaks.ManualBreakCount++
}
ws.RowBreaks.Count = len(ws.RowBreaks.Brk)
ws.ColBreaks.Count = len(ws.ColBreaks.Brk)
return err
}
// RemovePageBreak remove a page break by given worksheet name and cell
// reference.
func (f *File) RemovePageBreak(sheet, cell string) error {
var (
ws *xlsxWorksheet
row, col int
err error
)
if ws, err = f.workSheetReader(sheet); err != nil {
return err
}
if col, row, err = CellNameToCoordinates(cell); err != nil {
return err
}
col--
row--
if col == row && col == 0 {
return err
}
removeBrk := func(ID int, brks []*xlsxBrk) []*xlsxBrk {
for i, brk := range brks {
if brk.ID == ID {
brks = append(brks[:i], brks[i+1:]...)
}
}
return brks
}
if ws.RowBreaks == nil || ws.ColBreaks == nil {
return err
}
rowBrks := len(ws.RowBreaks.Brk)
colBrks := len(ws.ColBreaks.Brk)
if rowBrks > 0 && rowBrks == colBrks {
ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk)
ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk)
ws.RowBreaks.Count = len(ws.RowBreaks.Brk)
ws.ColBreaks.Count = len(ws.ColBreaks.Brk)
ws.RowBreaks.ManualBreakCount--
ws.ColBreaks.ManualBreakCount--
return err
}
if rowBrks > 0 && rowBrks > colBrks {
ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk)
ws.RowBreaks.Count = len(ws.RowBreaks.Brk)
ws.RowBreaks.ManualBreakCount--
return err
}
if colBrks > 0 && colBrks > rowBrks {
ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk)
ws.ColBreaks.Count = len(ws.ColBreaks.Brk)
ws.ColBreaks.ManualBreakCount--
}
return err
}
// relsReader provides a function to get the pointer to the structure
// after deserialization of relationships parts.
func (f *File) relsReader(path string) (*xlsxRelationships, error) {
rels, _ := f.Relationships.Load(path)
if rels == nil {
if _, ok := f.Pkg.Load(path); ok {
c := xlsxRelationships{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&c); err != nil && err != io.EOF {
return nil, err
}
f.Relationships.Store(path, &c)
}
}
if rels, _ = f.Relationships.Load(path); rels != nil {
return rels.(*xlsxRelationships), nil
}
return nil, nil
}
// fillSheetData ensures there are enough rows, and columns in the chosen
// row to accept data. Missing rows are backfilled and given their row number
// Uses the last populated row as a hint for the size of the next row to add
func (ws *xlsxWorksheet) prepareSheetXML(col, row int) {
rowCount := len(ws.SheetData.Row)
sizeHint := 0
var ht *float64
var customHeight bool
if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
ht = float64Ptr(ws.SheetFormatPr.DefaultRowHeight)
customHeight = true
}
if rowCount > 0 {
sizeHint = len(ws.SheetData.Row[rowCount-1].C)
}
if rowCount < row {
// append missing rows
for rowIdx := rowCount; rowIdx < row; rowIdx++ {
ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)})
}
}
rowData := &ws.SheetData.Row[row-1]
fillColumns(rowData, col, row)
}
// fillColumns fill cells in the column of the row as contiguous.
func fillColumns(rowData *xlsxRow, col, row int) {
cellCount := len(rowData.C)
if cellCount < col {
for colIdx := cellCount; colIdx < col; colIdx++ {
cellName, _ := CoordinatesToCellName(colIdx+1, row)
rowData.C = append(rowData.C, xlsxC{R: cellName})
}
}
}
// makeContiguousColumns make columns in specific rows as contiguous.
func (ws *xlsxWorksheet) makeContiguousColumns(fromRow, toRow, colCount int) {
for ; fromRow < toRow; fromRow++ {
rowData := &ws.SheetData.Row[fromRow-1]
fillColumns(rowData, colCount, fromRow)
}
}
// SetSheetDimension provides the method to set or remove the used range of the
// worksheet by a given range reference. It specifies the row and column bounds
// of used cells in the worksheet. The range reference is set using the A1
// reference style(e.g., "A1:D5"). Passing an empty range reference will remove
// the used range of the worksheet.
func (f *File) SetSheetDimension(sheet, rangeRef string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
// Remove the dimension element if an empty string is provided
if rangeRef == "" {
ws.Dimension = nil
return nil
}
parts := len(strings.Split(rangeRef, ":"))
if parts == 1 {
_, _, err = CellNameToCoordinates(rangeRef)
if err == nil {
ws.Dimension = &xlsxDimension{Ref: strings.ToUpper(rangeRef)}
}
return err
}
if parts != 2 {
return ErrParameterInvalid
}
coordinates, err := rangeRefToCoordinates(rangeRef)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
ref, err := coordinatesToRangeRef(coordinates)
ws.Dimension = &xlsxDimension{Ref: ref}
return err
}
// GetSheetDimension provides the method to get the used range of the worksheet.
func (f *File) GetSheetDimension(sheet string) (string, error) {
var ref string
if err := checkSheetName(sheet); err != nil {
return ref, err
}
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return ref, ErrSheetNotExist{sheet}
}
if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
ws := worksheet.(*xlsxWorksheet)
if ws.Dimension != nil {
ref = ws.Dimension.Ref
}
return ref, nil
}
needClose, decoder, tempFile, err := f.xmlDecoder(name)
if needClose && err == nil {
defer func() {
err = tempFile.Close()
}()
}
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch xmlElement := token.(type) {
case xml.StartElement:
if xmlElement.Name.Local == "dimension" {
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "ref" {
return attr.Value, err
}
}
}
if xmlElement.Name.Local == "sheetData" {
return ref, err
}
}
}
return ref, err
}
// AddIgnoredErrors provides the method to ignored error for a range of cells.
func (f *File) AddIgnoredErrors(sheet, rangeRef string, ignoredErrorsType IgnoredErrorsType) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if rangeRef == "" {
return ErrParameterInvalid
}
if ws.IgnoredErrors == nil {
ws.IgnoredErrors = &xlsxIgnoredErrors{}
}
ie := map[IgnoredErrorsType]xlsxIgnoredError{
IgnoredErrorsEvalError: {Sqref: rangeRef, EvalError: true},
IgnoredErrorsTwoDigitTextYear: {Sqref: rangeRef, TwoDigitTextYear: true},
IgnoredErrorsNumberStoredAsText: {Sqref: rangeRef, NumberStoredAsText: true},
IgnoredErrorsFormula: {Sqref: rangeRef, Formula: true},
IgnoredErrorsFormulaRange: {Sqref: rangeRef, FormulaRange: true},
IgnoredErrorsUnlockedFormula: {Sqref: rangeRef, UnlockedFormula: true},
IgnoredErrorsEmptyCellReference: {Sqref: rangeRef, EmptyCellReference: true},
IgnoredErrorsListDataValidation: {Sqref: rangeRef, ListDataValidation: true},
IgnoredErrorsCalculatedColumn: {Sqref: rangeRef, CalculatedColumn: true},
}[ignoredErrorsType]
for _, val := range ws.IgnoredErrors.IgnoredError {
if reflect.DeepEqual(val, ie) {
return err
}
}
ws.IgnoredErrors.IgnoredError = append(ws.IgnoredErrors.IgnoredError, ie)
return err
}
================================================
FILE: sheet_test.go
================================================
package excelize
import (
"encoding/xml"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSheet(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
sheetID, err := f.NewSheet("sheet2")
assert.NoError(t, err)
f.SetActiveSheet(sheetID)
// Test delete original sheet
idx, err := f.GetSheetIndex("Sheet1")
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet(f.GetSheetName(idx)))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
// Test create new worksheet with already exists name
sheetID, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
idx, err = f.GetSheetIndex("Sheet2")
assert.NoError(t, err)
assert.Equal(t, idx, sheetID)
// Test create new worksheet with empty sheet name
sheetID, err = f.NewSheet(":\\/?*[]")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
assert.Equal(t, -1, sheetID)
}
func TestPanes(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false}))
_, err := f.NewSheet("Panes 2")
assert.NoError(t, err)
expected := Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
}
assert.NoError(t, f.SetPanes("Panes 2", &expected))
panes, err := f.GetPanes("Panes 2")
assert.NoError(t, err)
assert.Equal(t, expected, panes)
_, err = f.NewSheet("Panes 3")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 3",
&Panes{
Freeze: false,
Split: true,
XSplit: 3270,
YSplit: 1800,
TopLeftCell: "N57",
ActivePane: "bottomLeft",
Selection: []Selection{
{SQRef: "I36", ActiveCell: "I36"},
{SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
{SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
{SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"},
},
},
))
_, err = f.NewSheet("Panes 4")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 4",
&Panes{
Freeze: true,
Split: false,
XSplit: 0,
YSplit: 9,
TopLeftCell: "A34",
ActivePane: "bottomLeft",
Selection: []Selection{
{SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
},
},
))
assert.EqualError(t, f.SetPanes("Panes 4", nil), ErrParameterInvalid.Error())
assert.EqualError(t, f.SetPanes("SheetN", nil), "sheet SheetN does not exist")
// Test set panes with invalid sheet name
assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
// Test get panes with empty sheet views
f = NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without panes
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without sheet views
ws.(*xlsxWorksheet).SheetViews = nil
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes on not exists worksheet
_, err = f.GetPanes("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test add pane on empty sheet views worksheet
f = NewFile()
f.checked = sync.Map{}
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``))
assert.NoError(t, f.SetPanes("Sheet1",
&Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
},
))
}
func TestSearchSheet(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
// Test search in a not exists worksheet
_, err = f.SearchSheet("Sheet4", "")
assert.EqualError(t, err, "sheet Sheet4 does not exist")
// Test search sheet with invalid sheet name
_, err = f.SearchSheet("Sheet:1", "")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
var expected []string
// Test search a not exists value
result, err := f.SearchSheet("Sheet1", "X")
assert.NoError(t, err)
assert.EqualValues(t, expected, result)
result, err = f.SearchSheet("Sheet1", "A")
assert.NoError(t, err)
assert.EqualValues(t, []string{"A1"}, result)
// Test search the coordinates where the numerical value in the range of
// "0-9" of Sheet1 is described by regular expression:
result, err = f.SearchSheet("Sheet1", "[0-9]", true)
assert.NoError(t, err)
assert.EqualValues(t, expected, result)
assert.NoError(t, f.Close())
// Test search worksheet data after set cell value
f = NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.SearchSheet("Sheet1", "")
assert.NoError(t, err)
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
f.checked = sync.Map{}
result, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
assert.Equal(t, []string(nil), result)
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
result, err = f.SearchSheet("Sheet1", "A")
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
assert.Equal(t, []string(nil), result)
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
result, err = f.SearchSheet("Sheet1", "A")
assert.Equal(t, newCoordinatesToCellNameError(1, 0), err)
assert.Equal(t, []string(nil), result)
// Test search sheet with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetPageLayout(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetPageLayout("Sheet1", nil))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).PageSetUp = nil
expected := PageLayoutOptions{
Size: intPtr(1),
Orientation: stringPtr("landscape"),
FirstPageNumber: uintPtr(1),
AdjustTo: uintPtr(120),
FitToHeight: intPtr(2),
FitToWidth: intPtr(2),
BlackAndWhite: boolPtr(true),
PageOrder: stringPtr("overThenDown"),
}
assert.NoError(t, f.SetPageLayout("Sheet1", &expected))
opts, err := f.GetPageLayout("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, opts)
// Test set page layout on not exists worksheet
assert.EqualError(t, f.SetPageLayout("SheetN", nil), "sheet SheetN does not exist")
// Test set page layout with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetPageLayout("Sheet:1", nil))
// Test set page layout with invalid parameters
assert.Equal(t, ErrPageSetupAdjustTo, f.SetPageLayout("Sheet1", &PageLayoutOptions{
AdjustTo: uintPtr(5),
}))
assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{
Orientation: stringPtr("x"),
}), "invalid Orientation value \"x\", acceptable value should be one of portrait, landscape")
assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{
PageOrder: stringPtr("x"),
}), "invalid PageOrder value \"x\", acceptable value should be one of overThenDown, downThenOver")
}
func TestGetPageLayout(t *testing.T) {
f := NewFile()
// Test get page layout on not exists worksheet
_, err := f.GetPageLayout("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get page layout with invalid sheet name
_, err = f.GetPageLayout("Sheet:1")
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
}
func TestHeaderFooter(t *testing.T) {
f := NewFile()
// Test get header and footer with default header and footer settings
opts, err := f.GetHeaderFooter("Sheet1")
assert.NoError(t, err)
assert.Equal(t, (*HeaderFooterOptions)(nil), opts)
// Test get header and footer on not exists worksheet
_, err = f.GetHeaderFooter("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter"))
// Test set header and footer on not exists worksheet
assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN does not exist")
// Test Sheet:1 with invalid sheet name
assert.EqualError(t, f.SetHeaderFooter("Sheet:1", nil), ErrSheetNameInvalid.Error())
// Test set header and footer with illegal setting
assert.EqualError(t, f.SetHeaderFooter("Sheet1", &HeaderFooterOptions{
OddHeader: strings.Repeat("c", MaxFieldLength+1),
}), newFieldLengthError("OddHeader").Error())
assert.NoError(t, f.SetHeaderFooter("Sheet1", nil))
text := strings.Repeat("一", MaxFieldLength)
assert.NoError(t, f.SetHeaderFooter("Sheet1", &HeaderFooterOptions{
OddHeader: text,
OddFooter: text,
EvenHeader: text,
EvenFooter: text,
FirstHeader: text,
}))
expected := &HeaderFooterOptions{
DifferentFirst: true,
DifferentOddEven: true,
OddHeader: "&R&P",
OddFooter: "&C&F",
EvenHeader: "&L&P",
EvenFooter: "&L&D&R&T",
FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`,
}
assert.NoError(t, f.SetHeaderFooter("Sheet1", expected))
opts, err = f.GetHeaderFooter("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, opts)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetHeaderFooter.xlsx")))
}
func TestDefinedName(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Amount.",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
Scope: "Sheet1",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: builtInDefinedNames[0],
RefersTo: "Sheet1!$A$1:$Z$100",
Scope: "Sheet1",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: builtInDefinedNames[1],
RefersTo: "Sheet1!$A:$A,Sheet1!$1:$1",
Scope: "Sheet1",
}))
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
}), ErrDefinedNameDuplicate.Error())
assert.EqualError(t, f.DeleteDefinedName(&DefinedName{
Name: "No Exist Defined Name",
}), ErrDefinedNameScope.Error())
// Test set defined name without name
assert.EqualError(t, f.SetDefinedName(&DefinedName{
RefersTo: "Sheet1!$A$2:$D$5",
}), ErrParameterInvalid.Error())
// Test set defined name without reference
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: "Amount",
}), ErrParameterInvalid.Error())
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
assert.NoError(t, f.DeleteDefinedName(&DefinedName{
Name: "Amount",
}))
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
assert.Len(t, f.GetDefinedName(), 3)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
// Test set defined name with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5",
}), "XML syntax error on line 1: invalid UTF-8")
// Test delete defined name with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteDefinedName(&DefinedName{Name: "Amount"}),
"XML syntax error on line 1: invalid UTF-8")
}
func TestGroupSheets(t *testing.T) {
f := NewFile()
sheets := []string{"Sheet2", "Sheet3"}
for _, sheet := range sheets {
_, err := f.NewSheet(sheet)
assert.NoError(t, err)
}
assert.EqualError(t, f.GroupSheets([]string{"Sheet1", "SheetN"}), "sheet SheetN does not exist")
assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet")
// Test group sheets with invalid sheet name
assert.EqualError(t, f.GroupSheets([]string{"Sheet:1", "Sheet1"}), ErrSheetNameInvalid.Error())
assert.NoError(t, f.GroupSheets([]string{"Sheet1", "Sheet2"}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGroupSheets.xlsx")))
}
func TestUngroupSheets(t *testing.T) {
f := NewFile()
sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"}
for _, sheet := range sheets {
_, err := f.NewSheet(sheet)
assert.NoError(t, err)
}
assert.NoError(t, f.UngroupSheets())
}
func TestInsertPageBreak(t *testing.T) {
f := NewFile()
assert.NoError(t, f.InsertPageBreak("Sheet1", "A1"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.EqualError(t, f.InsertPageBreak("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.InsertPageBreak("SheetN", "C3"), "sheet SheetN does not exist")
// Test insert page break with invalid sheet name
assert.EqualError(t, f.InsertPageBreak("Sheet:1", "C3"), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertPageBreak.xlsx")))
}
func TestRemovePageBreak(t *testing.T) {
f := NewFile()
assert.NoError(t, f.RemovePageBreak("Sheet1", "A2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "A2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "B2"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A1"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "C3"))
assert.NoError(t, f.InsertPageBreak("Sheet1", "A3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "B3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A3"))
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.InsertPageBreak("Sheet2", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet2", "C2"))
assert.NoError(t, f.RemovePageBreak("Sheet2", "B2"))
assert.EqualError(t, f.RemovePageBreak("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.RemovePageBreak("SheetN", "C3"), "sheet SheetN does not exist")
// Test remove page break with invalid sheet name
assert.EqualError(t, f.RemovePageBreak("Sheet:1", "A3"), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemovePageBreak.xlsx")))
}
func TestGetSheetName(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.Equal(t, "Sheet1", f.GetSheetName(0))
assert.Equal(t, "Sheet2", f.GetSheetName(1))
assert.Empty(t, f.GetSheetName(-1))
assert.Empty(t, f.GetSheetName(2))
assert.NoError(t, f.Close())
}
func TestGetSheetMap(t *testing.T) {
expectedMap := map[int]string{
1: "Sheet1",
2: "Sheet2",
}
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
sheetMap := f.GetSheetMap()
for idx, name := range sheetMap {
assert.Equal(t, expectedMap[idx], name)
}
assert.Len(t, sheetMap, 2)
assert.NoError(t, f.Close())
f = NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.getSheetMap()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetActiveSheet(t *testing.T) {
f := NewFile()
f.WorkBook.BookViews = nil
f.SetActiveSheet(1)
f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}}
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}}
f.SetActiveSheet(1)
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = nil
f.SetActiveSheet(1)
f = NewFile()
f.SetActiveSheet(-1)
assert.Equal(t, f.GetActiveSheetIndex(), 0)
f = NewFile()
f.WorkBook.BookViews = nil
idx, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
ws, ok = f.Sheet.Load("xl/worksheets/sheet2.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}}
f.SetActiveSheet(idx)
}
func TestSetSheetName(t *testing.T) {
f := NewFile()
// Test set worksheet with the same name
assert.NoError(t, f.SetSheetName("Sheet1", "Sheet1"))
assert.Equal(t, "Sheet1", f.GetSheetName(0))
// Test set worksheet with the different name
assert.NoError(t, f.SetSheetName("Sheet1", "sheet1"))
assert.Equal(t, "sheet1", f.GetSheetName(0))
// Test set sheet name with invalid sheet name
assert.Equal(t, f.SetSheetName("Sheet:1", "Sheet1"), ErrSheetNameInvalid)
_, err := f.NewSheet("Sheet 3")
assert.NoError(t, err)
// Test set worksheet name with existing defined name and auto filter
assert.NoError(t, f.AutoFilter("Sheet1", "A1:A2", nil))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name1",
RefersTo: "$B$2",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name2",
RefersTo: "$A1$2:A2",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name3",
RefersTo: "Sheet1!$A$1:'Sheet1'!A1:Sheet1!$A$1,Sheet1!A1:Sheet3!A1,Sheet3!A1",
}))
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name4",
RefersTo: "'Sheet 3'!$A1$2:A2",
}))
assert.NoError(t, f.SetSheetName("Sheet1", "Sheet 2"))
for i, expected := range []string{"'Sheet 2'!$A$1:$A$2", "$B$2", "$A1$2:A2", "'Sheet 2'!$A$1:'Sheet 2'!A1:'Sheet 2'!$A$1,'Sheet 2'!A1:Sheet3!A1,Sheet3!A1", "'Sheet 3'!$A1$2:A2"} {
assert.Equal(t, expected, f.WorkBook.DefinedNames.DefinedName[i].Data)
}
}
func TestWorksheetWriter(t *testing.T) {
f := NewFile()
// Test set cell value with alternate content
f.Sheet.Delete("xl/worksheets/sheet1.xml")
worksheet := xml.Header + `%d
`
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(worksheet, 1)))
f.checked = sync.Map{}
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 2))
f.workSheetWriter()
value, ok := f.Pkg.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
assert.Equal(t, fmt.Sprintf(worksheet, 2), string(value.([]byte)))
}
func TestGetWorkbookPath(t *testing.T) {
f := NewFile()
f.Pkg.Delete(defaultXMLPathRels)
assert.Empty(t, f.getWorkbookPath())
}
func TestGetWorkbookRelsPath(t *testing.T) {
f := NewFile()
f.Pkg.Delete("xl/_rels/.rels")
f.Pkg.Store(defaultXMLPathRels, []byte(xml.Header+``))
assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath())
}
func TestDeleteSheet(t *testing.T) {
f := NewFile()
idx, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
f.SetActiveSheet(idx)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet1"))
assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx")))
// Test with auto filter defined names
f = NewFile()
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
_, err = f.NewSheet("Sheet3")
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A"))
assert.NoError(t, f.AutoFilter("Sheet1", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet2", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet3", "A1:A1", nil))
assert.NoError(t, f.DeleteSheet("Sheet2"))
assert.NoError(t, f.DeleteSheet("Sheet1"))
// Test delete sheet with invalid sheet name
assert.EqualError(t, f.DeleteSheet("Sheet:1"), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx")))
}
func TestMoveSheet(t *testing.T) {
f := NewFile()
defer func() {
assert.NoError(t, f.Close())
}()
for i := 2; i < 6; i++ {
_, err := f.NewSheet("Sheet" + strconv.Itoa(i))
assert.NoError(t, err)
}
assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList())
// Move target to first position
assert.NoError(t, f.MoveSheet("Sheet2", "Sheet1"))
assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList())
assert.Equal(t, "Sheet1", f.GetSheetName(f.GetActiveSheetIndex()))
// Move target to last position
assert.NoError(t, f.MoveSheet("Sheet2", "Sheet5"))
assert.NoError(t, f.MoveSheet("Sheet5", "Sheet2"))
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList())
// Move target to same position
assert.NoError(t, f.MoveSheet("Sheet1", "Sheet1"))
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList())
// Test move sheet with invalid sheet name
assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("", "Sheet2"))
assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("Sheet1", ""))
// Test move sheet on not exists worksheet
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("SheetN", "Sheet2"))
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("Sheet1", "SheetN"))
// Test move sheet with unsupported workbook charset
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.MoveSheet("Sheet2", "Sheet1"), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteAndAdjustDefinedNames(t *testing.T) {
deleteAndAdjustDefinedNames(nil, 0)
deleteAndAdjustDefinedNames(&xlsxWorkbook{}, 0)
}
func TestGetSheetID(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet1")
assert.NoError(t, err)
id := f.getSheetID("sheet1")
assert.NotEqual(t, -1, id)
}
func TestSetSheetVisible(t *testing.T) {
f := NewFile()
// Test set sheet visible with invalid sheet name
assert.EqualError(t, f.SetSheetVisible("Sheet:1", false), ErrSheetNameInvalid.Error())
f.WorkBook.Sheets.Sheet[0].Name = "SheetN"
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist")
// Test set sheet visible with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "XML syntax error on line 1: invalid UTF-8")
// Test set sheet visible with empty sheet views
f = NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
ws, ok := f.Sheet.Load("xl/worksheets/sheet2.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = nil
assert.NoError(t, f.SetSheetVisible("Sheet2", false))
visible, err := f.GetSheetVisible("Sheet2")
assert.NoError(t, err)
assert.False(t, visible)
}
func TestGetSheetVisible(t *testing.T) {
f := NewFile()
// Test get sheet visible with invalid sheet name
visible, err := f.GetSheetVisible("Sheet:1")
assert.Equal(t, false, visible)
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
}
func TestGetSheetIndex(t *testing.T) {
f := NewFile()
// Test get sheet index with invalid sheet name
idx, err := f.GetSheetIndex("Sheet:1")
assert.Equal(t, -1, idx)
assert.EqualError(t, err, ErrSheetNameInvalid.Error())
}
func TestSetContentTypes(t *testing.T) {
f := NewFile()
// Test set content type with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8")
}
func TestRemoveContentTypesPart(t *testing.T) {
f := NewFile()
// Test delete sheet from content types with unsupported charset content types
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, "/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8")
}
func BenchmarkNewSheet(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
newSheetWithSet()
}
})
}
func newSheetWithSet() {
file := NewFile()
for i := 0; i < 1000; i++ {
_ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), int64(i))
}
file = nil
}
func BenchmarkFile_SaveAs(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
newSheetWithSave()
}
})
}
func newSheetWithSave() {
file := NewFile()
for i := 0; i < 1000; i++ {
_ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), int64(i))
}
_ = file.Save()
}
func TestAttrValToBool(t *testing.T) {
_, err := attrValToBool("hidden", []xml.Attr{
{Name: xml.Name{Local: "hidden"}},
})
assert.EqualError(t, err, `strconv.ParseBool: parsing "": invalid syntax`)
got, err := attrValToBool("hidden", []xml.Attr{
{Name: xml.Name{Local: "hidden"}, Value: "1"},
})
assert.NoError(t, err)
assert.Equal(t, true, got)
}
func TestAttrValToFloat(t *testing.T) {
_, err := attrValToFloat("ht", []xml.Attr{
{Name: xml.Name{Local: "ht"}},
})
assert.EqualError(t, err, `strconv.ParseFloat: parsing "": invalid syntax`)
got, err := attrValToFloat("ht", []xml.Attr{
{Name: xml.Name{Local: "ht"}, Value: "42.1"},
})
assert.NoError(t, err)
assert.Equal(t, 42.1, got)
}
func TestSetSheetBackgroundFromBytes(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetName("Sheet1", ".svg"))
for i, imageTypes := range []string{
".svg", ".bmp", ".emf", ".emz", ".gif",
".jpg", ".png", ".tif", ".wmf", ".wmz",
} {
file := fmt.Sprintf("excelize%s", imageTypes)
if i > 0 {
file = filepath.Join("test", "images", fmt.Sprintf("excel%s", imageTypes))
_, err := f.NewSheet(imageTypes)
assert.NoError(t, err)
}
img, err := os.Open(file)
assert.NoError(t, err)
content, err := io.ReadAll(img)
assert.NoError(t, err)
assert.NoError(t, img.Close())
assert.NoError(t, f.SetSheetBackgroundFromBytes(imageTypes, imageTypes, content))
}
// Test set worksheet background with invalid sheet name
img, err := os.Open(filepath.Join("test", "images", "excel.png"))
assert.NoError(t, err)
content, err := io.ReadAll(img)
assert.NoError(t, err)
assert.EqualError(t, f.SetSheetBackgroundFromBytes("Sheet:1", ".png", content), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackgroundFromBytes.xlsx")))
assert.NoError(t, f.Close())
assert.EqualError(t, f.SetSheetBackgroundFromBytes("Sheet1", ".svg", nil), ErrParameterInvalid.Error())
}
func TestCheckSheetName(t *testing.T) {
for expected, name := range map[error]string{
// Test valid sheet name
nil: "Sheet1",
nil: "She'et1",
// Test invalid sheet name, empty name
ErrSheetNameBlank: "",
// Test invalid sheet name, include :\/?*[]
ErrSheetNameInvalid: "Sheet:",
ErrSheetNameInvalid: `Sheet\`,
ErrSheetNameInvalid: "Sheet/",
ErrSheetNameInvalid: "Sheet?",
ErrSheetNameInvalid: "Sheet*",
ErrSheetNameInvalid: "Sheet[",
ErrSheetNameInvalid: "Sheet]",
// Test invalid sheet name, single quotes at the front or at the end
ErrSheetNameSingleQuote: "'Sheet",
ErrSheetNameSingleQuote: "Sheet'",
// Test invalid sheet name, exceed max length
ErrSheetNameLength: "Sheet" + strings.Repeat("\U0001F600", 14),
} {
assert.Equal(t, expected, checkSheetName(name))
}
}
func TestSheetDimension(t *testing.T) {
f := NewFile()
const sheetName = "Sheet1"
// Test get a new worksheet dimension
dimension, err := f.GetSheetDimension(sheetName)
assert.NoError(t, err)
assert.Equal(t, "A1", dimension)
// Test remove the worksheet dimension
assert.NoError(t, f.SetSheetDimension(sheetName, ""))
assert.NoError(t, err)
dimension, err = f.GetSheetDimension(sheetName)
assert.NoError(t, err)
assert.Empty(t, dimension)
// Test set the worksheet dimension
for _, excepted := range []string{"A1", "A1:D5", "A1:XFD1048576", "a1", "A1:d5"} {
err = f.SetSheetDimension(sheetName, excepted)
assert.NoError(t, err)
dimension, err := f.GetSheetDimension(sheetName)
assert.NoError(t, err)
assert.Equal(t, strings.ToUpper(excepted), dimension)
}
// Test set the worksheet dimension with invalid range reference or no exists worksheet
for _, c := range []struct {
sheetName string
rangeRef string
err string
}{
{"Sheet1", "A-1", "cannot convert cell \"A-1\" to coordinates: invalid cell name \"A-1\""},
{"Sheet1", "A1:B-1", "cannot convert cell \"B-1\" to coordinates: invalid cell name \"B-1\""},
{"Sheet1", "A1:XFD1048577", "row number exceeds maximum limit"},
{"Sheet1", "123", "cannot convert cell \"123\" to coordinates: invalid cell name \"123\""},
{"Sheet1", "A:B", "cannot convert cell \"A\" to coordinates: invalid cell name \"A\""},
{"Sheet1", ":B10", "cannot convert cell \"\" to coordinates: invalid cell name \"\""},
{"Sheet1", "XFE1", ErrColumnNumber.Error()},
{"Sheet1", "A1048577", "row number exceeds maximum limit"},
{"Sheet1", "ZZZ", "cannot convert cell \"ZZZ\" to coordinates: invalid cell name \"ZZZ\""},
{"SheetN", "A1", "sheet SheetN does not exist"},
{"Sheet1", "A1:B3:D5", ErrParameterInvalid.Error()},
} {
err = f.SetSheetDimension(c.sheetName, c.rangeRef)
assert.EqualError(t, err, c.err)
}
// Test get the worksheet dimension no exists worksheet
dimension, err = f.GetSheetDimension("SheetN")
assert.Empty(t, dimension)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get the worksheet dimension with blank worksheet name
dimension, err = f.GetSheetDimension("")
assert.Empty(t, dimension)
assert.Equal(t, err, ErrSheetNameBlank)
// Test get the worksheet dimension with in mode
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
dimension, err = f.GetSheetDimension("Sheet1")
assert.Equal(t, "A19:D22", dimension)
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test get the worksheet dimension in stream mode without dimension element
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
tempFile, ok := f.tempFiles.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
assert.NoError(t, os.WriteFile(tempFile.(string), fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value), 0o644))
dimension, err = f.GetSheetDimension("Sheet1")
assert.NoError(t, err)
assert.Empty(t, dimension)
// Test get the worksheet dimension in stream mode without sheetData element
assert.NoError(t, os.WriteFile(tempFile.(string), fmt.Appendf(nil, ``, NameSpaceSpreadSheet.Value), 0o644))
dimension, err = f.GetSheetDimension("Sheet1")
assert.NoError(t, err)
assert.Empty(t, dimension)
assert.NoError(t, f.Close())
}
func TestAddIgnoredErrors(t *testing.T) {
f := NewFile()
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEvalError))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEvalError))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsTwoDigitTextYear))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsNumberStoredAsText))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsFormula))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsFormulaRange))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsUnlockedFormula))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsEmptyCellReference))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsListDataValidation))
assert.NoError(t, f.AddIgnoredErrors("Sheet1", "A1", IgnoredErrorsCalculatedColumn))
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddIgnoredErrors("SheetN", "A1", IgnoredErrorsEvalError))
assert.Equal(t, ErrParameterInvalid, f.AddIgnoredErrors("Sheet1", "", IgnoredErrorsEvalError))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddIgnoredErrors.xlsx")))
assert.NoError(t, f.Close())
}
================================================
FILE: sheetpr.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "reflect"
// SetPageMargins provides a function to set worksheet page margins.
func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if opts == nil {
return err
}
preparePageMargins := func(ws *xlsxWorksheet) {
if ws.PageMargins == nil {
ws.PageMargins = new(xlsxPageMargins)
}
}
preparePrintOptions := func(ws *xlsxWorksheet) {
if ws.PrintOptions == nil {
ws.PrintOptions = new(xlsxPrintOptions)
}
}
s := reflect.ValueOf(opts).Elem()
for i := 0; i < 6; i++ {
if !s.Field(i).IsNil() {
preparePageMargins(ws)
name := s.Type().Field(i).Name
reflect.ValueOf(ws.PageMargins).Elem().FieldByName(name).Set(s.Field(i).Elem())
}
}
if opts.Horizontally != nil {
preparePrintOptions(ws)
ws.PrintOptions.HorizontalCentered = *opts.Horizontally
}
if opts.Vertically != nil {
preparePrintOptions(ws)
ws.PrintOptions.VerticalCentered = *opts.Vertically
}
return err
}
// GetPageMargins provides a function to get worksheet page margins.
func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
opts := PageLayoutMarginsOptions{
Bottom: float64Ptr(0.75),
Footer: float64Ptr(0.3),
Header: float64Ptr(0.3),
Left: float64Ptr(0.7),
Right: float64Ptr(0.7),
Top: float64Ptr(0.75),
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return opts, err
}
if ws.PageMargins != nil {
opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
opts.Footer = float64Ptr(ws.PageMargins.Footer)
opts.Header = float64Ptr(ws.PageMargins.Header)
opts.Left = float64Ptr(ws.PageMargins.Left)
opts.Right = float64Ptr(ws.PageMargins.Right)
opts.Top = float64Ptr(ws.PageMargins.Top)
}
if ws.PrintOptions != nil {
opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered)
opts.Vertically = boolPtr(ws.PrintOptions.VerticalCentered)
}
return opts, err
}
// prepareSheetPr create sheetPr element which not exist.
func (ws *xlsxWorksheet) prepareSheetPr() {
if ws.SheetPr == nil {
ws.SheetPr = new(xlsxSheetPr)
}
}
// setSheetOutlineProps set worksheet outline properties by given options.
func (ws *xlsxWorksheet) setSheetOutlineProps(opts *SheetPropsOptions) {
prepareOutlinePr := func(ws *xlsxWorksheet) {
ws.prepareSheetPr()
if ws.SheetPr.OutlinePr == nil {
ws.SheetPr.OutlinePr = new(xlsxOutlinePr)
}
}
if opts.OutlineSummaryBelow != nil {
prepareOutlinePr(ws)
ws.SheetPr.OutlinePr.SummaryBelow = opts.OutlineSummaryBelow
}
if opts.OutlineSummaryRight != nil {
prepareOutlinePr(ws)
ws.SheetPr.OutlinePr.SummaryRight = opts.OutlineSummaryRight
}
}
// setSheetProps set worksheet format properties by given options.
func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) {
preparePageSetUpPr := func(ws *xlsxWorksheet) {
ws.prepareSheetPr()
if ws.SheetPr.PageSetUpPr == nil {
ws.SheetPr.PageSetUpPr = new(xlsxPageSetUpPr)
}
}
prepareTabColor := func(ws *xlsxWorksheet) {
ws.prepareSheetPr()
if ws.SheetPr.TabColor == nil {
ws.SheetPr.TabColor = new(xlsxColor)
}
}
if opts.CodeName != nil {
ws.prepareSheetPr()
ws.SheetPr.CodeName = *opts.CodeName
}
if opts.EnableFormatConditionsCalculation != nil {
ws.prepareSheetPr()
ws.SheetPr.EnableFormatConditionsCalculation = opts.EnableFormatConditionsCalculation
}
if opts.Published != nil {
ws.prepareSheetPr()
ws.SheetPr.Published = opts.Published
}
if opts.AutoPageBreaks != nil {
preparePageSetUpPr(ws)
ws.SheetPr.PageSetUpPr.AutoPageBreaks = *opts.AutoPageBreaks
}
if opts.FitToPage != nil {
preparePageSetUpPr(ws)
ws.SheetPr.PageSetUpPr.FitToPage = *opts.FitToPage
}
ws.setSheetOutlineProps(opts)
s := reflect.ValueOf(opts).Elem()
for i := 5; i < 9; i++ {
if !s.Field(i).IsNil() {
prepareTabColor(ws)
name := s.Type().Field(i).Name
fld := reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:])
if s.Field(i).Kind() == reflect.Ptr && fld.Kind() == reflect.Ptr {
fld.Set(s.Field(i))
continue
}
fld.Set(s.Field(i).Elem())
}
}
}
// SetSheetProps provides a function to set worksheet properties.
func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if opts == nil {
return err
}
ws.setSheetProps(opts)
if ws.SheetFormatPr == nil {
ws.SheetFormatPr = &xlsxSheetFormatPr{DefaultRowHeight: defaultRowHeight}
}
s := reflect.ValueOf(opts).Elem()
for i := 11; i < 18; i++ {
if !s.Field(i).IsNil() {
name := s.Type().Field(i).Name
reflect.ValueOf(ws.SheetFormatPr).Elem().FieldByName(name).Set(s.Field(i).Elem())
}
}
return err
}
// GetSheetProps provides a function to get worksheet properties.
func (f *File) GetSheetProps(sheet string) (SheetPropsOptions, error) {
baseColWidth := uint8(8)
opts := SheetPropsOptions{
EnableFormatConditionsCalculation: boolPtr(true),
Published: boolPtr(true),
AutoPageBreaks: boolPtr(true),
OutlineSummaryBelow: boolPtr(true),
BaseColWidth: &baseColWidth,
}
ws, err := f.workSheetReader(sheet)
if err != nil {
return opts, err
}
if ws.SheetPr != nil {
opts.CodeName = stringPtr(ws.SheetPr.CodeName)
if ws.SheetPr.EnableFormatConditionsCalculation != nil {
opts.EnableFormatConditionsCalculation = ws.SheetPr.EnableFormatConditionsCalculation
}
if ws.SheetPr.Published != nil {
opts.Published = ws.SheetPr.Published
}
if ws.SheetPr.PageSetUpPr != nil {
opts.AutoPageBreaks = boolPtr(ws.SheetPr.PageSetUpPr.AutoPageBreaks)
opts.FitToPage = boolPtr(ws.SheetPr.PageSetUpPr.FitToPage)
}
if ws.SheetPr.OutlinePr != nil {
opts.OutlineSummaryBelow = ws.SheetPr.OutlinePr.SummaryBelow
opts.OutlineSummaryRight = ws.SheetPr.OutlinePr.SummaryRight
}
if ws.SheetPr.TabColor != nil {
opts.TabColorIndexed = intPtr(ws.SheetPr.TabColor.Indexed)
opts.TabColorRGB = stringPtr(ws.SheetPr.TabColor.RGB)
opts.TabColorTheme = ws.SheetPr.TabColor.Theme
opts.TabColorTint = float64Ptr(ws.SheetPr.TabColor.Tint)
}
}
if ws.SheetFormatPr != nil {
opts.BaseColWidth = &ws.SheetFormatPr.BaseColWidth
opts.DefaultColWidth = float64Ptr(ws.SheetFormatPr.DefaultColWidth)
opts.DefaultRowHeight = float64Ptr(ws.SheetFormatPr.DefaultRowHeight)
opts.CustomHeight = boolPtr(ws.SheetFormatPr.CustomHeight)
opts.ZeroHeight = boolPtr(ws.SheetFormatPr.ZeroHeight)
opts.ThickTop = boolPtr(ws.SheetFormatPr.ThickTop)
opts.ThickBottom = boolPtr(ws.SheetFormatPr.ThickBottom)
}
return opts, err
}
================================================
FILE: sheetpr_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSetPageMargins(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetPageMargins("Sheet1", nil))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).PageMargins = nil
ws.(*xlsxWorksheet).PrintOptions = nil
expected := PageLayoutMarginsOptions{
Bottom: float64Ptr(1.0),
Footer: float64Ptr(1.0),
Header: float64Ptr(1.0),
Left: float64Ptr(1.0),
Right: float64Ptr(1.0),
Top: float64Ptr(1.0),
Horizontally: boolPtr(true),
Vertically: boolPtr(true),
}
assert.NoError(t, f.SetPageMargins("Sheet1", &expected))
opts, err := f.GetPageMargins("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, opts)
// Test set page margins on not exists worksheet
assert.EqualError(t, f.SetPageMargins("SheetN", nil), "sheet SheetN does not exist")
// Test set page margins with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetPageMargins("Sheet:1", nil))
}
func TestGetPageMargins(t *testing.T) {
f := NewFile()
// Test get page margins on not exists worksheet
_, err := f.GetPageMargins("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get page margins with invalid sheet name
_, err = f.GetPageMargins("Sheet:1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
func TestSetSheetProps(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetProps("Sheet1", nil))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetPr = nil
ws.(*xlsxWorksheet).SheetFormatPr = nil
baseColWidth, enable := uint8(8), boolPtr(true)
expected := SheetPropsOptions{
CodeName: stringPtr("code"),
EnableFormatConditionsCalculation: enable,
Published: enable,
AutoPageBreaks: enable,
FitToPage: enable,
TabColorIndexed: intPtr(1),
TabColorRGB: stringPtr("FFFF00"),
TabColorTheme: intPtr(1),
TabColorTint: float64Ptr(1),
OutlineSummaryBelow: enable,
OutlineSummaryRight: enable,
BaseColWidth: &baseColWidth,
DefaultColWidth: float64Ptr(10),
DefaultRowHeight: float64Ptr(10),
CustomHeight: enable,
ZeroHeight: enable,
ThickTop: enable,
ThickBottom: enable,
}
assert.NoError(t, f.SetSheetProps("Sheet1", &expected))
opts, err := f.GetSheetProps("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, opts)
ws.(*xlsxWorksheet).SheetPr = nil
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{FitToPage: enable}))
ws.(*xlsxWorksheet).SheetPr = nil
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorRGB: stringPtr("FFFF00")}))
ws.(*xlsxWorksheet).SheetPr = nil
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTheme: intPtr(1)}))
ws.(*xlsxWorksheet).SheetPr = nil
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTint: float64Ptr(1)}))
// Test get column width after set base column width
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetSheetProps("Sheet2", &SheetPropsOptions{BaseColWidth: &baseColWidth}))
width, err := f.GetColWidth("Sheet2", "A")
assert.NoError(t, err)
assert.Equal(t, 8.0, width)
// Test set worksheet properties on not exists worksheet
assert.EqualError(t, f.SetSheetProps("SheetN", nil), "sheet SheetN does not exist")
// Test set worksheet properties with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.SetSheetProps("Sheet:1", nil))
}
func TestGetSheetProps(t *testing.T) {
f := NewFile()
// Test get worksheet properties on not exists worksheet
_, err := f.GetSheetProps("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get worksheet properties with invalid sheet name
_, err = f.GetSheetProps("Sheet:1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
================================================
FILE: sheetview.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
// getSheetView returns the SheetView object
func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return nil, err
}
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
}
}
if viewIndex < 0 {
if viewIndex < -len(ws.SheetViews.SheetView) {
return nil, newViewIdxError(viewIndex)
}
viewIndex = len(ws.SheetViews.SheetView) + viewIndex
} else if viewIndex >= len(ws.SheetViews.SheetView) {
return nil, newViewIdxError(viewIndex)
}
return &(ws.SheetViews.SheetView[viewIndex]), err
}
// setSheetView set sheet view by given options.
func (view *xlsxSheetView) setSheetView(opts *ViewOptions) {
if opts.DefaultGridColor != nil {
view.DefaultGridColor = opts.DefaultGridColor
}
if opts.RightToLeft != nil {
view.RightToLeft = *opts.RightToLeft
}
if opts.ShowFormulas != nil {
view.ShowFormulas = *opts.ShowFormulas
}
if opts.ShowGridLines != nil {
view.ShowGridLines = opts.ShowGridLines
}
if opts.ShowRowColHeaders != nil {
view.ShowRowColHeaders = opts.ShowRowColHeaders
}
if opts.ShowRuler != nil {
view.ShowRuler = opts.ShowRuler
}
if opts.ShowZeros != nil {
view.ShowZeros = opts.ShowZeros
}
if opts.TopLeftCell != nil {
view.TopLeftCell = *opts.TopLeftCell
}
if opts.View != nil {
if inStrSlice([]string{"normal", "pageLayout", "pageBreakPreview"}, *opts.View, true) != -1 {
view.View = *opts.View
}
}
if opts.ZoomScale != nil && *opts.ZoomScale >= 10 && *opts.ZoomScale <= 400 {
view.ZoomScale = *opts.ZoomScale
}
}
// SetSheetView sets sheet view options. The viewIndex may be negative and if
// so is counted backward (-1 is the last view).
func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error {
view, err := f.getSheetView(sheet, viewIndex)
if err != nil {
return err
}
if opts == nil {
return err
}
view.setSheetView(opts)
return nil
}
// GetSheetView gets the value of sheet view options. The viewIndex may be
// negative and if so is counted backward (-1 is the last view).
func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error) {
opts := ViewOptions{
DefaultGridColor: boolPtr(true),
ShowFormulas: boolPtr(true),
ShowGridLines: boolPtr(true),
ShowRowColHeaders: boolPtr(true),
ShowRuler: boolPtr(true),
ShowZeros: boolPtr(true),
View: stringPtr("normal"),
ZoomScale: float64Ptr(100),
}
view, err := f.getSheetView(sheet, viewIndex)
if err != nil {
return opts, err
}
if view.DefaultGridColor != nil {
opts.DefaultGridColor = view.DefaultGridColor
}
opts.RightToLeft = boolPtr(view.RightToLeft)
opts.ShowFormulas = boolPtr(view.ShowFormulas)
if view.ShowGridLines != nil {
opts.ShowGridLines = view.ShowGridLines
}
if view.ShowRowColHeaders != nil {
opts.ShowRowColHeaders = view.ShowRowColHeaders
}
if view.ShowRuler != nil {
opts.ShowRuler = view.ShowRuler
}
if view.ShowZeros != nil {
opts.ShowZeros = view.ShowZeros
}
opts.TopLeftCell = stringPtr(view.TopLeftCell)
if view.View != "" {
opts.View = stringPtr(view.View)
}
if view.ZoomScale >= 10 && view.ZoomScale <= 400 {
opts.ZoomScale = float64Ptr(view.ZoomScale)
}
return opts, err
}
================================================
FILE: sheetview_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSetView(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetView("Sheet1", -1, nil))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = nil
expected := ViewOptions{
DefaultGridColor: boolPtr(false),
RightToLeft: boolPtr(false),
ShowFormulas: boolPtr(false),
ShowGridLines: boolPtr(false),
ShowRowColHeaders: boolPtr(false),
ShowRuler: boolPtr(false),
ShowZeros: boolPtr(false),
TopLeftCell: stringPtr("A1"),
View: stringPtr("normal"),
ZoomScale: float64Ptr(120),
}
assert.NoError(t, f.SetSheetView("Sheet1", 0, &expected))
opts, err := f.GetSheetView("Sheet1", 0)
assert.NoError(t, err)
assert.Equal(t, expected, opts)
// Test set sheet view options with invalid view index
assert.EqualError(t, f.SetSheetView("Sheet1", 1, nil), "view index 1 out of range")
assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 out of range")
// Test set sheet view options on not exists worksheet
assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist")
}
func TestGetView(t *testing.T) {
f := NewFile()
_, err := f.getSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get sheet view options with invalid view index
_, err = f.GetSheetView("Sheet1", 1)
assert.EqualError(t, err, "view index 1 out of range")
_, err = f.GetSheetView("Sheet1", -2)
assert.EqualError(t, err, "view index -2 out of range")
// Test get sheet view options on not exists worksheet
_, err = f.GetSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
}
================================================
FILE: slicer.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"sort"
"strconv"
"strings"
"unicode"
)
// SlicerOptions represents the settings of the slicer.
//
// Name specifies the slicer name, should be an existing field name of the given
// table or pivot table, this setting is required.
//
// Cell specifies the left top cell coordinates the position for inserting the
// slicer, this setting is required.
//
// TableSheet specifies the worksheet name of the table or pivot table, this
// setting is required.
//
// TableName specifies the name of the table or pivot table, this setting is
// required.
//
// Caption specifies the caption of the slicer, this setting is optional.
//
// Macro used for set macro for the slicer, the workbook extension should be
// XLSM or XLTM.
//
// Width specifies the width of the slicer, this setting is optional.
//
// Height specifies the height of the slicer, this setting is optional.
//
// DisplayHeader specifies if display header of the slicer, this setting is
// optional, the default setting is display.
//
// ItemDesc specifies descending (Z-A) item sorting, this setting is optional,
// and the default setting is false (represents ascending).
//
// Format specifies the format of the slicer, this setting is optional.
type SlicerOptions struct {
slicerXML string
slicerCacheXML string
slicerCacheName string
slicerSheetName string
slicerSheetRID string
drawingXML string
Name string
Cell string
TableSheet string
TableName string
Caption string
Macro string
Width uint
Height uint
DisplayHeader *bool
ItemDesc bool
Format GraphicOptions
}
// AddSlicer function inserts a slicer by giving the worksheet name and slicer
// settings.
//
// For example, insert a slicer on the Sheet1!E1 with field Column1 for the
// table named Table1:
//
// err := f.AddSlicer("Sheet1", &excelize.SlicerOptions{
// Name: "Column1",
// Cell: "E1",
// TableSheet: "Sheet1",
// TableName: "Table1",
// Caption: "Column1",
// Width: 200,
// Height: 200,
// })
func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error {
opts, err := parseSlicerOptions(opts)
if err != nil {
return err
}
table, pivotTable, colIdx, err := f.getSlicerSource(opts)
if err != nil {
return err
}
extURI, ns := ExtURISlicerListX14, NameSpaceDrawingMLA14
if table != nil {
extURI = ExtURISlicerListX15
ns = NameSpaceDrawingMLSlicerX15
}
slicerID, err := f.addSheetSlicer(sheet, extURI)
if err != nil {
return err
}
slicerCacheName, err := f.setSlicerCache(colIdx, opts, table, pivotTable)
if err != nil {
return err
}
slicerName := f.genSlicerName(opts.Name)
if err := f.addDrawingSlicer(sheet, slicerName, ns, opts); err != nil {
return err
}
return f.addSlicer(slicerID, xlsxSlicer{
Name: slicerName,
Cache: slicerCacheName,
Caption: opts.Caption,
ShowCaption: opts.DisplayHeader,
RowHeight: 251883,
})
}
// parseSlicerOptions provides a function to parse the format settings of the
// slicer with default value.
func parseSlicerOptions(opts *SlicerOptions) (*SlicerOptions, error) {
if opts == nil {
return nil, ErrParameterRequired
}
if opts.Name == "" || opts.Cell == "" || opts.TableSheet == "" || opts.TableName == "" {
return nil, ErrParameterInvalid
}
if opts.Width == 0 {
opts.Width = defaultSlicerWidth
}
if opts.Height == 0 {
opts.Height = defaultSlicerHeight
}
format := opts.Format
graphicOptions, err := format.parseGraphicOptions(nil)
if err != nil {
return opts, err
}
opts.Format = *graphicOptions
return opts, err
}
// countSlicers provides a function to get slicer files count storage in the
// folder xl/slicers.
func (f *File) countSlicers() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicers/slicer") {
count++
}
return true
})
return count
}
// countSlicerCache provides a function to get slicer cache files count storage
// in the folder xl/SlicerCaches.
func (f *File) countSlicerCache() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
count++
}
return true
})
return count
}
// getSlicerSource returns the slicer data source table or pivot table settings
// and the index of the given slicer fields in the table or pivot table
// column.
func (f *File) getSlicerSource(opts *SlicerOptions) (*Table, *PivotTableOptions, int, error) {
var (
table *Table
pivotTable *PivotTableOptions
colIdx int
err error
dataRange string
tables []Table
pivotTables []PivotTableOptions
)
if tables, err = f.GetTables(opts.TableSheet); err != nil {
return table, pivotTable, colIdx, err
}
for _, tbl := range tables {
if tbl.Name == opts.TableName {
table = &tbl
dataRange = fmt.Sprintf("%s!%s", opts.TableSheet, tbl.Range)
break
}
}
if table == nil {
if pivotTables, err = f.GetPivotTables(opts.TableSheet); err != nil {
return table, pivotTable, colIdx, err
}
for _, tbl := range pivotTables {
if tbl.Name == opts.TableName {
pivotTable = &tbl
dataRange = tbl.DataRange
break
}
}
if pivotTable == nil {
return table, pivotTable, colIdx, newNoExistTableError(opts.TableName)
}
}
order, _ := f.getTableFieldsOrder(&PivotTableOptions{DataRange: dataRange})
if colIdx = inStrSlice(order, opts.Name, true); colIdx == -1 {
return table, pivotTable, colIdx, newInvalidSlicerNameError(opts.Name)
}
return table, pivotTable, colIdx, err
}
// addSheetSlicer adds a new slicer and updates the namespace and relationships
// parts of the worksheet by giving the worksheet name.
func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
var (
slicerID = f.countSlicers() + 1
ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst)
)
if err != nil {
return slicerID, err
}
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return slicerID, err
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == extURI {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID != "" {
sheetRelationshipsDrawingXML := f.getSheetRelationshipsTargetByID(sheet, slicer.RID)
slicerID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../slicers/slicer"), ".xml"))
return slicerID, err
}
}
}
}
}
sheetRelationshipsSlicerXML := "../slicers/slicer" + strconv.Itoa(slicerID) + ".xml"
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipSlicer, sheetRelationshipsSlicerXML, "")
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
return slicerID, f.addSheetTableSlicer(ws, rID, extURI)
}
// addSheetTableSlicer adds a new table slicer for the worksheet by giving the
// worksheet relationships ID and extension URI.
func (f *File) addSheetTableSlicer(ws *xlsxWorksheet, rID int, extURI string) error {
var (
decodeExtLst = new(decodeExtLst)
err error
slicerListBytes, extLstBytes []byte
)
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
}
slicerListBytes, _ = xml.Marshal(&xlsxX14SlicerList{
Slicer: []*xlsxX14Slicer{{RID: "rId" + strconv.Itoa(rID)}},
})
ext := &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: extURI, Content: string(slicerListBytes),
}
if extURI == ExtURISlicerListX15 {
ext.xmlns = []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}}
}
decodeExtLst.Ext = append(decodeExtLst.Ext, ext)
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
return err
}
// addSlicer adds a new slicer to the workbook by giving the slicer ID and
// settings.
func (f *File) addSlicer(slicerID int, slicer xlsxSlicer) error {
slicerXML := "xl/slicers/slicer" + strconv.Itoa(slicerID) + ".xml"
slicers, err := f.slicerReader(slicerXML)
if err != nil {
return err
}
if err := f.addContentTypePart(slicerID, "slicer"); err != nil {
return err
}
slicers.Slicer = append(slicers.Slicer, slicer)
output, err := xml.Marshal(slicers)
f.saveFileList(slicerXML, output)
return err
}
// genSlicerName generates a unique slicer cache name by giving the slicer name.
func (f *File) genSlicerName(name string) string {
var (
cnt int
slicerName string
names []string
)
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicers/slicer") {
slicers, err := f.slicerReader(k.(string))
if err != nil {
return true
}
for _, slicer := range slicers.Slicer {
names = append(names, slicer.Name)
}
}
if strings.Contains(k.(string), "xl/timelines/timeline") {
timelines, err := f.timelineReader(k.(string))
if err != nil {
return true
}
for _, timeline := range timelines.Timeline {
names = append(names, timeline.Name)
}
}
return true
})
slicerName = name
for {
tmp := slicerName
if cnt > 0 {
tmp = fmt.Sprintf("%s %d", slicerName, cnt)
}
if inStrSlice(names, tmp, true) == -1 {
slicerName = tmp
break
}
cnt++
}
return slicerName
}
// genSlicerCacheName generates a unique slicer cache name by giving the slicer name.
func (f *File) genSlicerCacheName(name string) string {
var (
cnt int
definedNames []string
slicerCacheName string
)
for _, dn := range f.GetDefinedName() {
if dn.Scope == "Workbook" {
definedNames = append(definedNames, dn.Name)
}
}
for i, c := range name {
if unicode.IsLetter(c) {
slicerCacheName += string(c)
continue
}
if i > 0 && (unicode.IsDigit(c) || c == '.') {
slicerCacheName += string(c)
continue
}
slicerCacheName += "_"
}
slicerCacheName = fmt.Sprintf("Slicer_%s", slicerCacheName)
for {
tmp := slicerCacheName
if cnt > 0 {
tmp = fmt.Sprintf("%s%d", slicerCacheName, cnt)
}
if inStrSlice(definedNames, tmp, true) == -1 {
slicerCacheName = tmp
break
}
cnt++
}
return slicerCacheName
}
// setSlicerCache check if a slicer cache already exists or add a new slicer
// cache by giving the column index, slicer, table options, and returns the
// slicer cache name.
func (f *File) setSlicerCache(colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) {
var ok bool
var slicerCacheName string
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
slicerCache, err := f.slicerCacheReader(k.(string))
if err != nil {
return true
}
if pivotTable != nil && slicerCache.PivotTables != nil {
for _, tbl := range slicerCache.PivotTables.PivotTable {
if tbl.Name == pivotTable.Name {
ok, slicerCacheName = true, slicerCache.Name
return false
}
}
}
if table == nil || slicerCache.ExtLst == nil {
return true
}
ext := new(xlsxExt)
_ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext)
if ext.URI == ExtURISlicerCacheDefinition {
tableSlicerCache := new(decodeTableSlicerCache)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache)
if tableSlicerCache.TableID == table.tID && tableSlicerCache.Column == colIdx+1 {
ok, slicerCacheName = true, slicerCache.Name
return false
}
}
}
return true
})
if ok {
return slicerCacheName, nil
}
slicerCacheName = f.genSlicerCacheName(opts.Name)
return slicerCacheName, f.addSlicerCache(slicerCacheName, colIdx, opts, table, pivotTable)
}
// slicerReader provides a function to get the pointer to the structure
// after deserialization of xl/slicers/slicer%d.xml.
func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) {
content, ok := f.Pkg.Load(slicerXML)
slicer := &xlsxSlicers{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(slicer); err != nil && err != io.EOF {
return nil, err
}
}
return slicer, nil
}
// slicerCacheReader provides a function to get the pointer to the structure
// after deserialization of xl/slicerCaches/slicerCache%d.xml.
func (f *File) slicerCacheReader(slicerCacheXML string) (*xlsxSlicerCacheDefinition, error) {
content, ok := f.Pkg.Load(slicerCacheXML)
slicerCache := &xlsxSlicerCacheDefinition{}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(slicerCache); err != nil && err != io.EOF {
return nil, err
}
}
return slicerCache, nil
}
// timelineReader provides a function to get the pointer to the structure
// after deserialization of xl/timelines/timeline%d.xml.
func (f *File) timelineReader(timelineXML string) (*xlsxTimelines, error) {
content, ok := f.Pkg.Load(timelineXML)
timeline := &xlsxTimelines{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(timeline); err != nil && err != io.EOF {
return nil, err
}
}
return timeline, nil
}
// addSlicerCache adds a new slicer cache by giving the slicer cache name,
// column index, slicer, and table or pivot table options.
func (f *File) addSlicerCache(slicerCacheName string, colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) error {
var (
sortOrder string
slicerCacheBytes, tableSlicerBytes, extLstBytes []byte
extURI = ExtURISlicerCachesX14
slicerCacheID = f.countSlicerCache() + 1
decodeExtLst = new(decodeExtLst)
slicerCache = xlsxSlicerCacheDefinition{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSX15: NameSpaceSpreadSheetX15.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
Name: slicerCacheName,
SourceName: opts.Name,
}
)
if opts.ItemDesc {
sortOrder = "descending"
}
if pivotTable != nil {
pivotCacheID, err := f.addPivotCacheSlicer(pivotTable)
if err != nil {
return err
}
slicerCache.PivotTables = &xlsxSlicerCachePivotTables{
PivotTable: []xlsxSlicerCachePivotTable{
{TabID: f.getSheetID(opts.TableSheet), Name: pivotTable.Name},
},
}
slicerCache.Data = &xlsxSlicerCacheData{
Tabular: &xlsxTabularSlicerCache{
PivotCacheID: pivotCacheID,
SortOrder: sortOrder,
ShowMissing: boolPtr(false),
Items: &xlsxTabularSlicerCacheItems{
Count: 1, I: []xlsxTabularSlicerCacheItem{{S: true}},
},
},
}
}
if table != nil {
tableSlicerBytes, _ = xml.Marshal(&xlsxTableSlicerCache{
TableID: table.tID,
Column: colIdx + 1,
SortOrder: sortOrder,
})
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}},
URI: ExtURISlicerCacheDefinition, Content: string(tableSlicerBytes),
})
extLstBytes, _ = xml.Marshal(decodeExtLst)
slicerCache.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
extURI = ExtURISlicerCachesX15
}
slicerCacheXML := "xl/slicerCaches/slicerCache" + strconv.Itoa(slicerCacheID) + ".xml"
slicerCacheBytes, _ = xml.Marshal(slicerCache)
f.saveFileList(slicerCacheXML, slicerCacheBytes)
if err := f.addContentTypePart(slicerCacheID, "slicerCache"); err != nil {
return err
}
if err := f.addWorkbookSlicerCache(slicerCacheID, extURI); err != nil {
return err
}
return f.SetDefinedName(&DefinedName{Name: slicerCacheName, RefersTo: formulaErrorNA})
}
// addPivotCacheSlicer adds a new slicer cache by giving the pivot table options
// and returns pivot table cache ID.
func (f *File) addPivotCacheSlicer(opts *PivotTableOptions) (int, error) {
var (
pivotCacheID int
pivotCacheBytes, extLstBytes []byte
decodeExtLst = new(decodeExtLst)
decodeX14PivotCacheDefinition = new(decodeX14PivotCacheDefinition)
)
pc, err := f.pivotCacheReader(opts.pivotCacheXML)
if err != nil {
return pivotCacheID, err
}
if pc.ExtLst != nil {
_ = f.xmlNewDecoder(strings.NewReader("" + pc.ExtLst.Ext + "")).Decode(decodeExtLst)
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURIPivotCacheDefinition {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeX14PivotCacheDefinition)
return decodeX14PivotCacheDefinition.PivotCacheID, err
}
}
}
pivotCacheID = f.genPivotCacheDefinitionID()
pivotCacheBytes, _ = xml.Marshal(&xlsxX14PivotCacheDefinition{PivotCacheID: pivotCacheID})
ext := &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: ExtURIPivotCacheDefinition, Content: string(pivotCacheBytes),
}
decodeExtLst.Ext = append(decodeExtLst.Ext, ext)
extLstBytes, _ = xml.Marshal(decodeExtLst)
pc.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
pivotCache, err := xml.Marshal(pc)
f.saveFileList(opts.pivotCacheXML, pivotCache)
return pivotCacheID, err
}
// addDrawingSlicer adds a slicer shape and fallback shape by giving the
// worksheet name, slicer name, and slicer options.
func (f *File) addDrawingSlicer(sheet, slicerName string, ns xml.Attr, opts *SlicerOptions) error {
drawingID := f.countDrawings() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
content, cellAnchor, cNvPrID, err := f.cellAnchorShape(sheet, drawingXML, opts.Cell, opts.Width, opts.Height, opts.Format)
if err != nil {
return err
}
graphicFrame := xlsxGraphicFrame{
Macro: opts.Macro,
NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Name: slicerName,
Descr: opts.Format.AltText,
},
},
Xfrm: xlsxXfrm{Off: xlsxOff{}, Ext: xlsxPositiveSize2D{}},
Graphic: &xlsxGraphic{
GraphicData: &xlsxGraphicData{
URI: NameSpaceDrawingMLSlicer.Value,
Sle: &xlsxSle{XMLNS: NameSpaceDrawingMLSlicer.Value, Name: slicerName},
},
},
}
graphic, _ := xml.Marshal(graphicFrame)
sp := xdrSp{
Macro: opts.Macro,
NvSpPr: &xdrNvSpPr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Descr: opts.Format.AltText,
},
CNvSpPr: &xdrCNvSpPr{
TxBox: true,
},
},
SpPr: &xlsxSpPr{
Xfrm: xlsxXfrm{Off: xlsxOff{X: 2914650, Y: 152400}, Ext: xlsxPositiveSize2D{Cx: 1828800, Cy: 2238375}},
SolidFill: &aSolidFill{SrgbClr: &aSrgbClr{Val: stringPtr("FFFFFF")}},
PrstGeom: xlsxPrstGeom{
Prst: "rect",
},
Ln: xlsxLineProperties{W: 1, SolidFill: &xlsxInnerXML{Content: ""}},
},
TxBody: &xdrTxBody{
BodyPr: &aBodyPr{VertOverflow: "clip", HorzOverflow: "clip"},
P: []*aP{
{R: &aR{T: "This shape represents a table slicer. Table slicers are not supported in this version of Excel."}},
{R: &aR{T: "If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used."}},
},
},
}
shape, _ := xml.Marshal(sp)
cellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Format.Locked,
FPrintsWithSheet: *opts.Format.PrintObject,
}
choice := xlsxChoice{Requires: ns.Name.Local, Content: string(graphic)}
if ns.Value == NameSpaceDrawingMLA14.Value { // pivot table slicer
choice.XMLNSA14 = ns.Value
}
if ns.Value == NameSpaceDrawingMLSlicerX15.Value { // table slicer
choice.XMLNSSle15 = ns.Value
}
fallback := xlsxFallback{Content: string(shape)}
choiceBytes, _ := xml.Marshal(choice)
shapeBytes, _ := xml.Marshal(fallback)
cellAnchor.AlternateContent = append(cellAnchor.AlternateContent, &xlsxAlternateContent{
XMLNSMC: SourceRelationshipCompatibility.Value,
Content: string(choiceBytes) + string(shapeBytes),
})
if opts.Format.Positioning == "oneCell" {
content.OneCellAnchor = append(content.OneCellAnchor, cellAnchor)
} else {
content.TwoCellAnchor = append(content.TwoCellAnchor, cellAnchor)
}
f.Drawings.Store(drawingXML, content)
return f.addContentTypePart(drawingID, "drawings")
}
// addWorkbookSlicerCache add the association ID of the slicer cache in
// workbook.xml.
func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error {
var (
wb *xlsxWorkbook
err error
idx int
appendMode bool
decodeExtLst = new(decodeExtLst)
decodeSlicerCaches = new(decodeSlicerCaches)
x14SlicerCaches = new(xlsxX14SlicerCaches)
x15SlicerCaches = new(xlsxX15SlicerCaches)
ext *xlsxExt
slicerCacheBytes, slicerCachesBytes, extLstBytes []byte
)
if wb, err = f.workbookReader(); err != nil {
return err
}
rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipSlicerCache, fmt.Sprintf("/xl/slicerCaches/slicerCache%d.xml", slicerCacheID), "")
if wb.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("" + wb.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == URI {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeSlicerCaches)
slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)}
slicerCacheBytes, _ = xml.Marshal(slicerCache)
if URI == ExtURISlicerCachesX14 { // pivot table slicer
x14SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes)
x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches)
}
if URI == ExtURISlicerCachesX15 { // table slicer
x15SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes)
x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches)
}
decodeExtLst.Ext[idx].Content = string(slicerCachesBytes)
appendMode = true
}
}
}
if !appendMode {
slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)}
slicerCacheBytes, _ = xml.Marshal(slicerCache)
if URI == ExtURISlicerCachesX14 {
x14SlicerCaches.Content = string(slicerCacheBytes)
x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches)
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: ExtURISlicerCachesX14, Content: string(slicerCachesBytes),
})
}
if URI == ExtURISlicerCachesX15 {
x15SlicerCaches.Content = string(slicerCacheBytes)
x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches)
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}},
URI: ExtURISlicerCachesX15, Content: string(slicerCachesBytes),
})
}
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
return err
}
// GetSlicers provides the method to get all slicers in a worksheet by a given
// worksheet name. Note that, this function does not support getting the height,
// width, and graphic options of the slicer shape currently.
func (f *File) GetSlicers(sheet string) ([]SlicerOptions, error) {
var (
slicers []SlicerOptions
ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst)
)
if err != nil {
return slicers, err
}
if ws.ExtLst == nil {
return slicers, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return slicers, err
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(&slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID != "" {
opts, err := f.getSlicers(sheet, slicer.RID, drawingXML)
if err != nil {
return slicers, err
}
slicers = append(slicers, opts...)
}
}
}
}
return slicers, err
}
// getSlicerCache provides a function to get a slicer cache by given slicer
// cache name and slicer options.
func (f *File) getSlicerCache(slicerCacheName string, opt *SlicerOptions) *xlsxSlicerCacheDefinition {
var (
err error
slicerCache *xlsxSlicerCacheDefinition
)
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
slicerCache, err = f.slicerCacheReader(k.(string))
if err != nil {
return true
}
if slicerCache.Name == slicerCacheName {
opt.slicerCacheXML = k.(string)
return false
}
}
return true
})
return slicerCache
}
// getSlicers provides a function to get slicers options by given worksheet
// name, slicer part relationship ID and drawing part path.
func (f *File) getSlicers(sheet, rID, drawingXML string) ([]SlicerOptions, error) {
var (
opts []SlicerOptions
sheetRelationshipsSlicerXML = f.getSheetRelationshipsTargetByID(sheet, rID)
slicerXML = strings.ReplaceAll(sheetRelationshipsSlicerXML, "..", "xl")
slicers, err = f.slicerReader(slicerXML)
)
if err != nil {
return opts, err
}
for _, slicer := range slicers.Slicer {
opt := SlicerOptions{
slicerXML: slicerXML,
slicerCacheName: slicer.Cache,
slicerSheetName: sheet,
slicerSheetRID: rID,
drawingXML: drawingXML,
Name: slicer.Name,
Caption: slicer.Caption,
DisplayHeader: slicer.ShowCaption,
}
slicerCache := f.getSlicerCache(slicer.Cache, &opt)
if slicerCache == nil {
return opts, err
}
if err := f.extractTableSlicer(slicerCache, &opt); err != nil {
return opts, err
}
if err := f.extractPivotTableSlicer(slicerCache, &opt); err != nil {
return opts, err
}
if err = f.extractSlicerCellAnchor(drawingXML, &opt); err != nil {
return opts, err
}
opts = append(opts, opt)
}
return opts, err
}
// extractTableSlicer extract table slicer options from slicer cache.
func (f *File) extractTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
if slicerCache.ExtLst != nil {
tables, err := f.getTables()
if err != nil {
return err
}
ext := new(xlsxExt)
_ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext)
if ext.URI == ExtURISlicerCacheDefinition {
tableSlicerCache := new(decodeTableSlicerCache)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache)
opt.ItemDesc = tableSlicerCache.SortOrder == "descending"
for sheetName, sheetTables := range tables {
for _, table := range sheetTables {
if tableSlicerCache.TableID == table.tID {
opt.TableName = table.Name
opt.TableSheet = sheetName
}
}
}
}
}
return nil
}
// extractPivotTableSlicer extract pivot table slicer options from slicer cache.
func (f *File) extractPivotTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
pivotTables, err := f.getPivotTables()
if err != nil {
return err
}
if slicerCache.PivotTables != nil {
for _, pt := range slicerCache.PivotTables.PivotTable {
opt.TableName = pt.Name
for sheetName, sheetPivotTables := range pivotTables {
for _, pivotTable := range sheetPivotTables {
if opt.TableName == pivotTable.Name {
opt.TableSheet = sheetName
}
}
}
}
if slicerCache.Data != nil && slicerCache.Data.Tabular != nil {
opt.ItemDesc = slicerCache.Data.Tabular.SortOrder == "descending"
}
}
return err
}
// extractSlicerCellAnchor extract slicer drawing object from two cell anchor by
// giving drawing part path and slicer options.
func (f *File) extractSlicerCellAnchor(drawingXML string, opt *SlicerOptions) error {
var (
wsDr *xlsxWsDr
err error
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return err
}
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
for _, anchor := range wsDr.OneCellAnchor {
if err = f.extractSlicerFromAnchor(anchor, opt); err != nil {
return err
}
if err = f.extractSlicerFromDecodeAnchor(anchor, opt); err != nil {
return err
}
}
for _, anchor := range wsDr.TwoCellAnchor {
if err = f.extractSlicerFromAnchor(anchor, opt); err != nil {
return err
}
if err = f.extractSlicerFromDecodeAnchor(anchor, opt); err != nil {
return err
}
}
return err
}
// extractSlicerFromAnchor extract slicer drawing object from cell anchor by
// giving drawing part path and slicer options.
func (f *File) extractSlicerFromAnchor(anchor *xdrCellAnchor, opt *SlicerOptions) error {
var (
deChoice = new(decodeChoice)
err error
)
for _, ac := range anchor.AlternateContent {
if ac != nil {
_ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice)
if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value {
if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name {
if anchor.From != nil {
opt.Macro = deChoice.GraphicFrame.Macro
if opt.Cell, err = CoordinatesToCellName(anchor.From.Col+1, anchor.From.Row+1); err != nil {
return err
}
}
return err
}
}
}
}
return err
}
// extractSlicerFromDecodeAnchor extract slicer drawing object from decode cell
// anchor by giving drawing part path and slicer options.
func (f *File) extractSlicerFromDecodeAnchor(anchor *xdrCellAnchor, opt *SlicerOptions) error {
var (
deCellAnchor = new(decodeCellAnchor)
deChoice = new(decodeChoice)
err error
)
_ = f.xmlNewDecoder(strings.NewReader("" + anchor.GraphicFrame + "")).Decode(&deCellAnchor)
for _, ac := range deCellAnchor.AlternateContent {
if ac != nil {
_ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice)
if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value {
if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name {
if deCellAnchor.From != nil {
opt.Macro = deChoice.GraphicFrame.Macro
if opt.Cell, err = CoordinatesToCellName(deCellAnchor.From.Col+1, deCellAnchor.From.Row+1); err != nil {
return err
}
}
return err
}
}
}
}
return err
}
// getAllSlicers provides a function to get all slicers in a workbook.
func (f *File) getAllSlicers() (map[string][]SlicerOptions, error) {
slicers := map[string][]SlicerOptions{}
for _, sheetName := range f.GetSheetList() {
sles, err := f.GetSlicers(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return slicers, err
}
slicers[sheetName] = append(slicers[sheetName], sles...)
}
return slicers, nil
}
// DeleteSlicer provides the method to delete a slicer by a given slicer name.
func (f *File) DeleteSlicer(name string) error {
sles, err := f.getAllSlicers()
if err != nil {
return err
}
for _, slicers := range sles {
for _, slicer := range slicers {
if slicer.Name != name {
continue
}
_ = f.deleteSlicer(slicer)
return f.deleteSlicerCache(sles, slicer)
}
}
return newNoExistSlicerError(name)
}
// getSlicers provides a function to delete slicer by given slicer options.
func (f *File) deleteSlicer(opts SlicerOptions) error {
slicers, err := f.slicerReader(opts.slicerXML)
if err != nil {
return err
}
for i := 0; i < len(slicers.Slicer); i++ {
if slicers.Slicer[i].Name == opts.Name {
slicers.Slicer = append(slicers.Slicer[:i], slicers.Slicer[i+1:]...)
i--
}
}
if len(slicers.Slicer) == 0 {
var (
extLstBytes []byte
ws, err = f.workSheetReader(opts.slicerSheetName)
decodeExtLst = new(decodeExtLst)
)
if err != nil {
return err
}
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for i, ext := range decodeExtLst.Ext {
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID == opts.slicerSheetRID {
decodeExtLst.Ext = append(decodeExtLst.Ext[:i], decodeExtLst.Ext[i+1:]...)
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
f.Pkg.Delete(opts.slicerXML)
_ = f.removeContentTypesPart(ContentTypeSlicer, "/"+opts.slicerXML)
f.deleteSheetRelationships(opts.slicerSheetName, opts.slicerSheetRID)
return err
}
}
}
}
}
output, err := xml.Marshal(slicers)
f.saveFileList(opts.slicerXML, output)
return err
}
// deleteSlicerCache provides a function to delete the slicer cache by giving
// slicer options if the slicer cache is no longer used.
func (f *File) deleteSlicerCache(sles map[string][]SlicerOptions, opts SlicerOptions) error {
for _, slicers := range sles {
for _, slicer := range slicers {
if slicer.Name != opts.Name && slicer.slicerCacheName == opts.slicerCacheName {
return nil
}
}
}
if err := f.DeleteDefinedName(&DefinedName{Name: opts.slicerCacheName}); err != nil {
return err
}
f.Pkg.Delete(opts.slicerCacheXML)
return f.removeContentTypesPart(ContentTypeSlicerCache, "/"+opts.slicerCacheXML)
}
================================================
FILE: slicer_test.go
================================================
package excelize
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSlicer(t *testing.T) {
f := NewFile()
disable, colName := false, "_!@#$%^&*()-+=|\\/<>"
assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName))
// Create table in a worksheet
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "I1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: colName,
Cell: "M1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: colName,
Macro: "Button1_Click",
Width: 200,
Height: 200,
DisplayHeader: &disable,
ItemDesc: true,
Format: GraphicOptions{Positioning: "oneCell"},
}))
// Test get table slicers
slicers, err := f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "Column1", slicers[0].Name)
assert.Equal(t, "E1", slicers[0].Cell)
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
assert.Equal(t, "Table1", slicers[0].TableName)
assert.Equal(t, "Column1", slicers[0].Caption)
assert.Equal(t, "Column1 1", slicers[1].Name)
assert.Equal(t, "I1", slicers[1].Cell)
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
assert.Equal(t, "Table1", slicers[1].TableName)
assert.Equal(t, "Column1", slicers[1].Caption)
assert.Equal(t, colName, slicers[2].Name)
assert.Equal(t, "M1", slicers[2].Cell)
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
assert.Equal(t, "Table1", slicers[2].TableName)
assert.Equal(t, colName, slicers[2].Caption)
assert.Equal(t, "Button1_Click", slicers[2].Macro)
assert.False(t, *slicers[2].DisplayHeader)
assert.True(t, slicers[2].ItemDesc)
// Test create two pivot tables in a new worksheet
_, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
// Create some data in a sheet
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
year := []int{2017, 2018, 2019}
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
region := []string{"East", "West", "North", "South"}
assert.NoError(t, f.SetSheetRow("Sheet2", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
for row := 2; row < 32; row++ {
assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("B%d", row), year[rand.Intn(3)]))
assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("D%d", row), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet2", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
}
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet2!A1:E31",
PivotTableRange: "Sheet2!G2:M34",
Name: "PivotTable1",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Filter: []PivotTableField{{Data: "Region"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
ShowError: true,
PivotTableStyleName: "PivotStyleLight16",
}))
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet2!A1:E31",
PivotTableRange: "Sheet2!U34:O2",
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}},
RowGrandTotals: true,
ColGrandTotals: true,
ShowDrill: true,
ShowRowHeaders: true,
ShowColHeaders: true,
ShowLastColumn: true,
}))
// Test add a pivot table slicer
assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Month",
Cell: "G42",
TableSheet: "Sheet2",
TableName: "PivotTable1",
Caption: "Month",
}))
// Test add a pivot table slicer with duplicate field name
assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Month",
Cell: "K42",
TableSheet: "Sheet2",
TableName: "PivotTable1",
Caption: "Month",
}))
// Test add a pivot table slicer for another pivot table in a worksheet
assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Region",
Cell: "O42",
TableSheet: "Sheet2",
TableName: "PivotTable2",
Caption: "Region",
ItemDesc: true,
}))
// Test get pivot table slicers
slicers, err = f.GetSlicers("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "Month", slicers[0].Name)
assert.Equal(t, "G42", slicers[0].Cell)
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
assert.Equal(t, "PivotTable1", slicers[0].TableName)
assert.Equal(t, "Month", slicers[0].Caption)
assert.Equal(t, "Month 1", slicers[1].Name)
assert.Equal(t, "K42", slicers[1].Cell)
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
assert.Equal(t, "PivotTable1", slicers[1].TableName)
assert.Equal(t, "Month", slicers[1].Caption)
assert.Equal(t, "Region", slicers[2].Name)
assert.Equal(t, "O42", slicers[2].Cell)
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
assert.Equal(t, "PivotTable2", slicers[2].TableName)
assert.Equal(t, "Region", slicers[2].Caption)
assert.True(t, slicers[2].ItemDesc)
// Test add a table slicer with empty slicer options
assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil))
// Test add a table slicer with invalid slicer options
for _, opts := range []*SlicerOptions{
{Cell: "Q1", TableSheet: "Sheet1", TableName: "Table1"},
{Name: "Column", Cell: "Q1", TableSheet: "Sheet1"},
{Name: "Column", TableSheet: "Sheet1", TableName: "Table1"},
} {
assert.Equal(t, ErrParameterInvalid, f.AddSlicer("Sheet1", opts))
}
// Test add a table slicer with not exist worksheet
assert.EqualError(t, f.AddSlicer("SheetN", &SlicerOptions{
Name: "Column2",
Cell: "Q1",
TableSheet: "SheetN",
TableName: "Table1",
}), "sheet SheetN does not exist")
// Test add slicer with invalid positioning types
assert.Equal(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column2",
Cell: "Q1",
TableSheet: "Sheet1",
TableName: "Table1",
Format: GraphicOptions{Positioning: "x"},
}), newInvalidOptionalValue("Positioning", "x", supportedPositioning))
// Test add a table slicer with not exist table name
assert.Equal(t, newNoExistTableError("Table2"), f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column2",
Cell: "Q1",
TableSheet: "Sheet1",
TableName: "Table2",
}))
// Test add a table slicer with invalid slicer name
assert.Equal(t, newInvalidSlicerNameError("Column6"), f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column6",
Cell: "Q1",
TableSheet: "Sheet1",
TableName: "Table1",
}))
workbookPath := filepath.Join("test", "TestAddSlicer.xlsm")
file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin"))
assert.NoError(t, err)
assert.NoError(t, f.AddVBAProject(file))
assert.NoError(t, f.SaveAs(workbookPath))
assert.NoError(t, f.Close())
// Test add a pivot table slicer with unsupported charset pivot table
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/pivotTables/pivotTable2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Month",
Cell: "G42",
TableSheet: "Sheet2",
TableName: "PivotTable1",
Caption: "Month",
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test open a workbook and get already exist slicers
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "Column1", slicers[0].Name)
assert.Equal(t, "E1", slicers[0].Cell)
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
assert.Equal(t, "Table1", slicers[0].TableName)
assert.Equal(t, "Column1", slicers[0].Caption)
assert.Equal(t, "Column1 1", slicers[1].Name)
assert.Equal(t, "I1", slicers[1].Cell)
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
assert.Equal(t, "Table1", slicers[1].TableName)
assert.Equal(t, "Column1", slicers[1].Caption)
assert.Equal(t, colName, slicers[2].Name)
assert.Equal(t, "M1", slicers[2].Cell)
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
assert.Equal(t, "Table1", slicers[2].TableName)
assert.Equal(t, colName, slicers[2].Caption)
assert.Equal(t, "Button1_Click", slicers[2].Macro)
assert.False(t, *slicers[2].DisplayHeader)
assert.True(t, slicers[2].ItemDesc)
slicers, err = f.GetSlicers("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "Month", slicers[0].Name)
assert.Equal(t, "G42", slicers[0].Cell)
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
assert.Equal(t, "PivotTable1", slicers[0].TableName)
assert.Equal(t, "Month", slicers[0].Caption)
assert.Equal(t, "Month 1", slicers[1].Name)
assert.Equal(t, "K42", slicers[1].Cell)
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
assert.Equal(t, "PivotTable1", slicers[1].TableName)
assert.Equal(t, "Month", slicers[1].Caption)
assert.Equal(t, "Region", slicers[2].Name)
assert.Equal(t, "O42", slicers[2].Cell)
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
assert.Equal(t, "PivotTable2", slicers[2].TableName)
assert.Equal(t, "Region", slicers[2].Caption)
assert.True(t, slicers[2].ItemDesc)
// Test add a pivot table slicer with workbook which contains timeline
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/timelines/timeline1.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX15.Value)))
assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Month",
Cell: "G42",
TableSheet: "Sheet2",
TableName: "PivotTable1",
Caption: "Month",
}))
assert.NoError(t, f.Close())
// Test add a pivot table slicer with unsupported charset timeline
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/timelines/timeline1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.AddSlicer("Sheet2", &SlicerOptions{
Name: "Month",
Cell: "G42",
TableSheet: "Sheet2",
TableName: "PivotTable1",
Caption: "Month",
}))
assert.NoError(t, f.Close())
// Test add a table slicer with invalid worksheet extension list
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: "<>"}
assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
}))
assert.NoError(t, f.Close())
// Test add a table slicer with unsupported charset slicer
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
f.Pkg.Store("xl/slicers/slicer2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableName: "Table1",
TableSheet: "Sheet1",
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test add a table slicer with read workbook error
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
f.WorkBook.ExtLst = &xlsxExtLst{Ext: "<>"}
assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableName: "Table1",
TableSheet: "Sheet1",
}))
assert.NoError(t, f.Close())
// Test add a table slicer with unsupported charset content types
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableName: "Table1",
TableSheet: "Sheet1",
}), "XML syntax error on line 1: invalid UTF-8")
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSlicer(0, xlsxSlicer{}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
f = NewFile()
// Create table in a worksheet
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
f.Pkg.Store("xl/drawings/drawing2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
f = NewFile()
// Test get sheet slicers without slicer
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Empty(t, slicers)
// Test get sheet slicers with not exist worksheet name
_, err = f.GetSlicers("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
// Test get sheet slicers with unsupported charset slicer cache
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
// Test get sheet slicers with unsupported charset slicer
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with invalid worksheet extension list
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
_, err = f.GetSlicers("Sheet1")
assert.Error(t, err)
assert.NoError(t, f.Close())
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
// Test get sheet slicers without slicer cache
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
f.Pkg.Delete(k.(string))
}
return true
})
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Empty(t, slicers)
assert.NoError(t, f.Close())
// Test open a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`-1-1
`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value)))
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
assert.NoError(t, f.Close())
// Test open a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
// Test open a workbook and get sheet slicer with invalid cell reference in the drawing two cell anchor
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`-1-1
`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value)))
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
// Test get sheet slicer without slicer shape in the drawing part
f.Drawings.Delete("xl/drawings/drawing1.xml")
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(``, NameSpaceDrawingMLSpreadSheet.Value)))
_, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
f.Drawings.Delete("xl/drawings/drawing1.xml")
// Test get sheet slicers with unsupported charset drawing part
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with unsupported charset table
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with unsupported charset pivot table
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet2")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test create a workbook and get sheet slicer with invalid cell reference in the drawing one cell anchor
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
Format: GraphicOptions{Positioning: "oneCell"},
}))
drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml")
assert.True(t, ok)
drawing.(*xlsxWsDr).OneCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1}
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
assert.NoError(t, f.Close())
// Test create a workbook and get sheet slicer with invalid cell reference in the drawing two cell anchor
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
drawing, ok = f.Drawings.Load("xl/drawings/drawing1.xml")
assert.True(t, ok)
drawing.(*xlsxWsDr).TwoCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1}
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
assert.NoError(t, f.Close())
// Test open a workbook and delete slicers
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
for _, name := range []string{colName, "Column1 1", "Column1"} {
assert.NoError(t, f.DeleteSlicer(name))
}
for _, name := range []string{"Month", "Month 1", "Region"} {
assert.NoError(t, f.DeleteSlicer(name))
}
// Test delete slicer with no exits slicer name
assert.Equal(t, newNoExistSlicerError("x"), f.DeleteSlicer("x"))
assert.NoError(t, f.Close())
// Test open a workbook and delete sheet slicer with unsupported charset slicer cache
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteSlicer("Column1"), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestAddSheetSlicer(t *testing.T) {
f := NewFile()
// Test add sheet slicer with not exist worksheet name
_, err := f.addSheetSlicer("SheetN", ExtURISlicerListX15)
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
}
func TestAddSheetTableSlicer(t *testing.T) {
f := NewFile()
// Test add sheet table slicer with invalid worksheet extension
assert.Error(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: "<>"}}, 0, ExtURISlicerListX15))
// Test add sheet table slicer with existing worksheet extension
assert.NoError(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: fmt.Sprintf("", ExtURITimelineRefs)}}, 1, ExtURISlicerListX15))
assert.NoError(t, f.Close())
}
func TestSetSlicerCache(t *testing.T) {
f := NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
_, err := f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err)
assert.NoError(t, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err)
assert.NoError(t, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err)
assert.NoError(t, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
assert.NoError(t, err)
assert.NoError(t, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value)))
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
assert.NoError(t, err)
assert.NoError(t, f.Close())
}
func TestDeleteSlicer(t *testing.T) {
f, slicerXML := NewFile(), "xl/slicers/slicer1.xml"
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
// Test delete sheet slicers with invalid worksheet extension list
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
assert.Error(t, f.deleteSlicer(SlicerOptions{
slicerXML: slicerXML,
slicerSheetName: "Sheet1",
Name: "Column1",
}))
// Test delete slicer with unsupported charset worksheet
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicer(SlicerOptions{
slicerXML: slicerXML,
slicerSheetName: "Sheet1",
Name: "Column1",
}), "XML syntax error on line 1: invalid UTF-8")
// Test delete slicer with unsupported charset slicer
f.Pkg.Store(slicerXML, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicer(SlicerOptions{slicerXML: slicerXML}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestDeleteSlicerCache(t *testing.T) {
f := NewFile()
// Test delete slicer cache with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicerCache(nil, SlicerOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestAddSlicerCache(t *testing.T) {
f := NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSlicerCache("Slicer1", 0, &SlicerOptions{}, &Table{}, nil), "XML syntax error on line 1: invalid UTF-8")
// Test add a pivot table cache slicer with unsupported charset
pivotCacheXML := "xl/pivotCache/pivotCacheDefinition1.xml"
f.Pkg.Store(pivotCacheXML, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSlicerCache("Slicer1", 0, &SlicerOptions{}, nil,
&PivotTableOptions{pivotCacheXML: pivotCacheXML}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestAddDrawingSlicer(t *testing.T) {
f := NewFile()
// Test add a drawing slicer with not exist worksheet
assert.EqualError(t, f.addDrawingSlicer("SheetN", "Column2", NameSpaceDrawingMLSlicerX15, &SlicerOptions{
Name: "Column2",
Cell: "Q1",
TableSheet: "SheetN",
TableName: "Table1",
}), "sheet SheetN does not exist")
// Test add a drawing slicer with invalid cell reference
assert.EqualError(t, f.addDrawingSlicer("Sheet1", "Column2", NameSpaceDrawingMLSlicerX15, &SlicerOptions{
Name: "Column2",
Cell: "A",
TableSheet: "Sheet1",
TableName: "Table1",
}), "cannot convert cell \"A\" to coordinates: invalid cell name \"A\"")
assert.NoError(t, f.Close())
}
func TestAddWorkbookSlicerCache(t *testing.T) {
// Test add a workbook slicer cache with unsupported charset workbook
f := NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.addWorkbookSlicerCache(1, ExtURISlicerCachesX15), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestGenSlicerCacheName(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "Slicer_Column_1", RefersTo: formulaErrorNA}))
assert.Equal(t, "Slicer_Column_11", f.genSlicerCacheName("Column 1"))
assert.NoError(t, f.Close())
}
func TestAddPivotCacheSlicer(t *testing.T) {
f := NewFile()
pivotCacheXML := "xl/pivotCache/pivotCacheDefinition1.xml"
// Test add a pivot table cache slicer with existing extension list
f.Pkg.Store(pivotCacheXML, []byte(fmt.Sprintf(``, NameSpaceSpreadSheet.Value, ExtURIPivotCacheDefinition)))
_, err := f.addPivotCacheSlicer(&PivotTableOptions{
pivotCacheXML: pivotCacheXML,
})
assert.NoError(t, err)
}
================================================
FILE: sparkline.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"io"
"sort"
"strings"
)
// getSparklineGroupPresets returns the preset list of sparkline group to create
// x14:sparklineGroups element.
func getSparklineGroupPresets() []*xlsxX14SparklineGroup {
return []*xlsxX14SparklineGroup{
{
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(5)},
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(4)},
ColorLow: &xlsxColor{Theme: intPtr(4)},
}, // 0
{
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(5)},
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(4)},
ColorLow: &xlsxColor{Theme: intPtr(4)},
}, // 1
{
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(6)},
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(5)},
ColorLow: &xlsxColor{Theme: intPtr(5)},
}, // 2
{
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(7)},
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(6)},
ColorLow: &xlsxColor{Theme: intPtr(6)},
}, // 3
{
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(8)},
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(7)},
ColorLow: &xlsxColor{Theme: intPtr(7)},
}, // 4
{
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(9)},
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(8)},
ColorLow: &xlsxColor{Theme: intPtr(8)},
}, // 5
{
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(4)},
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
ColorHigh: &xlsxColor{Theme: intPtr(9)},
ColorLow: &xlsxColor{Theme: intPtr(9)},
}, // 6
{
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(5)},
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(5)},
ColorLow: &xlsxColor{Theme: intPtr(5)},
}, // 7
{
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(6)},
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
}, // 8
{
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(7)},
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
}, // 9
{
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(8)},
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
}, // 10
{
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(9)},
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
}, // 11
{
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorNegative: &xlsxColor{Theme: intPtr(4)},
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
}, // 12
{
ColorSeries: &xlsxColor{Theme: intPtr(4)},
ColorNegative: &xlsxColor{Theme: intPtr(5)},
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
}, // 13
{
ColorSeries: &xlsxColor{Theme: intPtr(5)},
ColorNegative: &xlsxColor{Theme: intPtr(6)},
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
}, // 14
{
ColorSeries: &xlsxColor{Theme: intPtr(6)},
ColorNegative: &xlsxColor{Theme: intPtr(7)},
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
}, // 15
{
ColorSeries: &xlsxColor{Theme: intPtr(7)},
ColorNegative: &xlsxColor{Theme: intPtr(8)},
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
}, // 16
{
ColorSeries: &xlsxColor{Theme: intPtr(8)},
ColorNegative: &xlsxColor{Theme: intPtr(9)},
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
}, // 17
{
ColorSeries: &xlsxColor{Theme: intPtr(9)},
ColorNegative: &xlsxColor{Theme: intPtr(4)},
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
}, // 18
{
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
}, // 19
{
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
}, // 20
{
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
}, // 21
{
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
}, // 22
{
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
}, // 23
{
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: 0.79998168889431442},
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
}, // 24
{
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.499984740745262},
ColorNegative: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
ColorMarkers: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
}, // 25
{
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.34998626667073579},
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
ColorMarkers: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
ColorFirst: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
ColorLast: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
ColorHigh: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
ColorLow: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
}, // 26
{
ColorSeries: &xlsxColor{RGB: "FF323232"},
ColorNegative: &xlsxColor{RGB: "FFD00000"},
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
ColorFirst: &xlsxColor{RGB: "FFD00000"},
ColorLast: &xlsxColor{RGB: "FFD00000"},
ColorHigh: &xlsxColor{RGB: "FFD00000"},
ColorLow: &xlsxColor{RGB: "FFD00000"},
}, // 27
{
ColorSeries: &xlsxColor{RGB: "FF000000"},
ColorNegative: &xlsxColor{RGB: "FF0070C0"},
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
ColorFirst: &xlsxColor{RGB: "FF0070C0"},
ColorLast: &xlsxColor{RGB: "FF0070C0"},
ColorHigh: &xlsxColor{RGB: "FF0070C0"},
ColorLow: &xlsxColor{RGB: "FF0070C0"},
}, // 28
{
ColorSeries: &xlsxColor{RGB: "FF376092"},
ColorNegative: &xlsxColor{RGB: "FFD00000"},
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
ColorFirst: &xlsxColor{RGB: "FFD00000"},
ColorLast: &xlsxColor{RGB: "FFD00000"},
ColorHigh: &xlsxColor{RGB: "FFD00000"},
ColorLow: &xlsxColor{RGB: "FFD00000"},
}, // 29
{
ColorSeries: &xlsxColor{RGB: "FF0070C0"},
ColorNegative: &xlsxColor{RGB: "FF000000"},
ColorMarkers: &xlsxColor{RGB: "FF000000"},
ColorFirst: &xlsxColor{RGB: "FF000000"},
ColorLast: &xlsxColor{RGB: "FF000000"},
ColorHigh: &xlsxColor{RGB: "FF000000"},
ColorLow: &xlsxColor{RGB: "FF000000"},
}, // 30
{
ColorSeries: &xlsxColor{RGB: "FF5F5F5F"},
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
ColorFirst: &xlsxColor{RGB: "FF5687C2"},
ColorLast: &xlsxColor{RGB: "FF359CEB"},
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
ColorLow: &xlsxColor{RGB: "FFFF5055"},
}, // 31
{
ColorSeries: &xlsxColor{RGB: "FF5687C2"},
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
ColorFirst: &xlsxColor{RGB: "FF777777"},
ColorLast: &xlsxColor{RGB: "FF359CEB"},
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
ColorLow: &xlsxColor{RGB: "FFFF5055"},
}, // 32
{
ColorSeries: &xlsxColor{RGB: "FFC6EFCE"},
ColorNegative: &xlsxColor{RGB: "FFFFC7CE"},
ColorMarkers: &xlsxColor{RGB: "FF8CADD6"},
ColorFirst: &xlsxColor{RGB: "FFFFDC47"},
ColorLast: &xlsxColor{RGB: "FFFFEB9C"},
ColorHigh: &xlsxColor{RGB: "FF60D276"},
ColorLow: &xlsxColor{RGB: "FFFF5367"},
}, // 33
{
ColorSeries: &xlsxColor{RGB: "FF00B050"},
ColorNegative: &xlsxColor{RGB: "FFFF0000"},
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
ColorFirst: &xlsxColor{RGB: "FFFFC000"},
ColorLast: &xlsxColor{RGB: "FFFFC000"},
ColorHigh: &xlsxColor{RGB: "FF00B050"},
ColorLow: &xlsxColor{RGB: "FFFF0000"},
}, // 34
{
ColorSeries: &xlsxColor{Theme: intPtr(3)},
ColorNegative: &xlsxColor{Theme: intPtr(9)},
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
ColorFirst: &xlsxColor{Theme: intPtr(4)},
ColorLast: &xlsxColor{Theme: intPtr(5)},
ColorHigh: &xlsxColor{Theme: intPtr(6)},
ColorLow: &xlsxColor{Theme: intPtr(7)},
}, // 35
{
ColorSeries: &xlsxColor{Theme: intPtr(1)},
ColorNegative: &xlsxColor{Theme: intPtr(9)},
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
ColorFirst: &xlsxColor{Theme: intPtr(4)},
ColorLast: &xlsxColor{Theme: intPtr(5)},
ColorHigh: &xlsxColor{Theme: intPtr(6)},
ColorLow: &xlsxColor{Theme: intPtr(7)},
}, // 36
}
}
// AddSparkline provides a function to add sparklines to the worksheet by
// given formatting options. Sparklines are small charts that fit in a single
// cell and are used to show trends in data. Sparklines are a feature of Excel
// 2010 and later only. You can write them to workbook that can be read by Excel
// 2007, but they won't be displayed. For example, add a grouped sparkline.
// Changes are applied to all three:
//
// err := f.AddSparkline("Sheet1", &excelize.SparklineOptions{
// Location: []string{"A1", "A2", "A3"},
// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"},
// Markers: true,
// })
//
// The following shows the formatting options of sparkline supported by excelize:
//
// Parameter | Description
// -------------+--------------------------------------------
// Location | Required, must have the same number with 'Range' parameter
// Range | Required, must have the same number with 'Location' parameter
// Type | Enumeration value: line, column, win_loss
// Style | Value range: 0 - 35
// Hight | Toggle sparkline high points
// Low | Toggle sparkline low points
// First | Toggle sparkline first points
// Last | Toggle sparkline last points
// Negative | Toggle sparkline negative points
// Markers | Toggle sparkline markers
// Axis | Used to specify if show horizontal axis
// Reverse | Used to specify if enable plot data right-to-left
// SeriesColor | An RGB Color is specified as RRGGBB
func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error {
var (
err error
ws *xlsxWorksheet
sparkType string
sparkTypes map[string]string
specifiedSparkTypes string
ok bool
group *xlsxX14SparklineGroup
groups *xlsxX14SparklineGroups
)
// parameter validation
if ws, err = f.parseFormatAddSparklineSet(sheet, opts); err != nil {
return err
}
// Handle the sparkline type
sparkType = "line"
sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
if opts.Type != "" {
if specifiedSparkTypes, ok = sparkTypes[opts.Type]; !ok {
err = ErrSparklineType
return err
}
sparkType = specifiedSparkTypes
}
group = getSparklineGroupPresets()[opts.Style]
group.Type = sparkType
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
group.DisplayEmptyCellsAs = "gap"
group.High = opts.High
group.Low = opts.Low
group.First = opts.First
group.Last = opts.Last
group.Negative = opts.Negative
group.DisplayXAxis = opts.Axis
group.Markers = opts.Markers
if opts.SeriesColor != "" {
group.ColorSeries = &xlsxColor{
RGB: getPaletteColor(opts.SeriesColor),
}
}
if opts.Reverse {
group.RightToLeft = opts.Reverse
}
f.addSparkline(opts, group)
if err = f.appendSparkline(ws, group, groups); err != nil {
return err
}
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
return err
}
// parseFormatAddSparklineSet provides a function to validate sparkline
// properties.
func (f *File) parseFormatAddSparklineSet(sheet string, opts *SparklineOptions) (*xlsxWorksheet, error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return ws, err
}
if opts == nil {
return ws, ErrParameterRequired
}
if len(opts.Location) < 1 {
return ws, ErrSparklineLocation
}
if len(opts.Range) < 1 {
return ws, ErrSparklineRange
}
// The range and locations must match
if len(opts.Location) != len(opts.Range) {
return ws, ErrSparkline
}
if opts.Style < 0 || 35 < opts.Style {
return ws, ErrSparklineStyle
}
if ws.ExtLst == nil {
ws.ExtLst = &xlsxExtLst{}
}
return ws, err
}
// addSparkline provides a function to create a sparkline in a sparkline group
// by given properties.
func (f *File) addSparkline(opts *SparklineOptions, group *xlsxX14SparklineGroup) {
for idx, location := range opts.Location {
group.Sparklines.Sparkline = append(group.Sparklines.Sparkline, &xlsxX14Sparkline{
F: opts.Range[idx],
Sqref: location,
})
}
}
// appendSparkline provides a function to append sparkline to sparkline
// groups.
func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) error {
var (
err error
idx int
appendMode bool
decodeExtLst = new(decodeExtLst)
decodeSparklineGroups *decodeX14SparklineGroups
ext *xlsxExt
sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte
)
sparklineGroupBytes, _ = xml.Marshal(group)
if ws.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == ExtURISparklineGroups {
decodeSparklineGroups = new(decodeX14SparklineGroups)
if err = f.xmlNewDecoder(strings.NewReader(ext.Content)).
Decode(decodeSparklineGroups); err != nil && err != io.EOF {
return err
}
if groups == nil {
groups = &xlsxX14SparklineGroups{}
}
groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value
groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes)
sparklineGroupsBytes, _ = xml.Marshal(groups)
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
appendMode = true
}
}
}
if !appendMode {
sparklineGroupsBytes, _ = xml.Marshal(&xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
SparklineGroups: []*xlsxX14SparklineGroup{group},
})
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes),
})
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
return err
}
================================================
FILE: sparkline_test.go
================================================
package excelize
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddSparkline(t *testing.T) {
f, err := prepareSparklineDataset()
assert.NoError(t, err)
// Set the columns widths to make the output clearer
style, err := f.NewStyle(&Style{Font: &Font{Bold: true}})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
viewOpts, err := f.GetSheetView("Sheet1", 0)
assert.NoError(t, err)
viewOpts.ZoomScale = float64Ptr(150)
assert.NoError(t, f.SetSheetView("Sheet1", 0, &viewOpts))
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 14))
assert.NoError(t, f.SetColWidth("Sheet1", "B", "B", 50))
// Headings
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Sparkline"))
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Description"))
assert.NoError(t, f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"},
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A3"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A4"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B6", "Line with markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A6"},
Range: []string{"Sheet3!A1:J1"},
Markers: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B7", "Line with high and low points."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A7"},
Range: []string{"Sheet3!A1:J1"},
High: true,
Low: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B8", "Line with first and last point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A8"},
Range: []string{"Sheet3!A1:J1"},
First: true,
Last: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B9", "Line with negative point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A9"},
Range: []string{"Sheet3!A1:J1"},
Negative: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B10", "Line with axis."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A10"},
Range: []string{"Sheet3!A1:J1"},
Axis: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B12", "Column with default style (1)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A12"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B13", "Column with style 2."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A13"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 2,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B14", "Column with style 3."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A14"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 3,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B15", "Column with style 4."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A15"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 4,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B16", "Column with style 5."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A16"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 5,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B17", "Column with style 6."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A17"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 6,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B18", "Column with a user defined color."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A18"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
SeriesColor: "E965E0",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B20", "A win/loss sparkline."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A20"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A21"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
Negative: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B23", "A left to right column (the default)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A23"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B24", "A right to left column."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A24"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
Reverse: true,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A25"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))
assert.NoError(t, f.SetCellValue("Sheet1", "A25", "Growth"))
assert.NoError(t, f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A27", "A28", "A29"},
Range: []string{"Sheet3!A5:J5", "Sheet3!A6:J6", "Sheet3!A7:J7"},
Markers: true,
}))
// Sheet2 sections
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "win_loss",
Negative: true,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
Location: []string{"F1"},
Range: []string{"Sheet2!A1:E1"},
Markers: true,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
Location: []string{"F2"},
Range: []string{"Sheet2!A2:E2"},
Type: "column",
Style: 12,
}))
assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "win_loss",
Negative: true,
}))
// Save spreadsheet by the given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSparkline.xlsx")))
// Test error exceptions
assert.EqualError(t, f.AddSparkline("SheetN", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
}), "sheet SheetN does not exist")
assert.Equal(t, ErrParameterRequired, f.AddSparkline("Sheet1", nil))
// Test add sparkline with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.AddSparkline("Sheet:1", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "win_loss",
Negative: true,
}))
assert.Equal(t, ErrSparklineLocation, f.AddSparkline("Sheet1", &SparklineOptions{
Range: []string{"Sheet2!A3:E3"},
}))
assert.Equal(t, ErrSparklineRange, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"F3"},
}))
assert.Equal(t, ErrSparkline, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"F2", "F3"},
Range: []string{"Sheet2!A3:E3"},
}))
assert.Equal(t, ErrSparklineType, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Type: "unknown_type",
}))
assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Style: -1,
}))
assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"F3"},
Range: []string{"Sheet2!A3:E3"},
Style: -1,
}))
// Test creating a conditional format with existing extension lists
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURISlicerListX14, ExtURISparklineGroups)}
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A3"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))
// Test creating a conditional format with invalid extension list characters
ws.(*xlsxWorksheet).ExtLst.Ext = fmt.Sprintf(``, ExtURISparklineGroups)
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"},
}), "XML syntax error on line 1: element closed by ")
}
func TestAppendSparkline(t *testing.T) {
// Test unsupported charset.
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)}
assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8")
}
func prepareSparklineDataset() (*File, error) {
f := NewFile()
sheet2 := [][]int{
{-2, 2, 3, -1, 0},
{30, 20, 33, 20, 15},
{1, -1, -1, 1, -1},
}
sheet3 := [][]int{
{-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
{30, 20, 33, 20, 15, 5, 5, 15, 10, 15},
{1, 1, -1, -1, 1, -1, 1, 1, 1, -1},
{5, 6, 7, 10, 15, 20, 30, 50, 70, 100},
{-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
{3, -1, 0, -2, 3, 2, 1, 0, 2, 1},
{0, -2, 3, 2, 1, 0, 1, 2, 3, 1},
}
if _, err := f.NewSheet("Sheet2"); err != nil {
return f, err
}
if _, err := f.NewSheet("Sheet3"); err != nil {
return f, err
}
for row, data := range sheet2 {
if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err)
}
}
for row, data := range sheet3 {
if err := f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err)
}
}
return f, nil
}
================================================
FILE: stream.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
"time"
)
// StreamWriter defined the type of stream writer.
type StreamWriter struct {
file *File
Sheet string
SheetID int
sheetWritten bool
worksheet *xlsxWorksheet
rawData bufferedWriter
rows int
mergeCellsCount int
mergeCells strings.Builder
tableParts string
}
// NewStreamWriter returns stream writer struct by given worksheet name used for
// writing data on a new existing empty worksheet with large amounts of data.
// Note that after writing data with the stream writer for the worksheet, you
// must call the 'Flush' method to end the streaming writing process, ensure
// that the order of row numbers is ascending when set rows, and the normal
// mode functions and stream mode functions can not be work mixed to writing
// data on the worksheets. The stream writer will try to use temporary files on
// disk to reduce the memory usage when in-memory chunks data over 16MB, and
// you can't get cell value at this time. For example, set data for worksheet
// of size 102400 rows x 50 columns with numbers and style:
//
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// sw, err := f.NewStreamWriter("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// styleID, err := f.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "777777"}})
// if err != nil {
// fmt.Println(err)
// return
// }
// if err := sw.SetRow("A1",
// []interface{}{
// excelize.Cell{StyleID: styleID, Value: "Data"},
// []excelize.RichTextRun{
// {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}},
// {Text: "Text", Font: &excelize.Font{Color: "e83723"}},
// },
// },
// excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
// fmt.Println(err)
// return
// }
// for rowID := 2; rowID <= 102400; rowID++ {
// row := make([]interface{}, 50)
// for colID := 0; colID < 50; colID++ {
// row[colID] = rand.Intn(640000)
// }
// cell, err := excelize.CoordinatesToCellName(1, rowID)
// if err != nil {
// fmt.Println(err)
// break
// }
// if err := sw.SetRow(cell, row); err != nil {
// fmt.Println(err)
// break
// }
// }
// if err := sw.Flush(); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
//
// Set cell value and cell formula for a worksheet with stream writer:
//
// err := sw.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1},
// excelize.Cell{Value: 2},
// excelize.Cell{Formula: "SUM(A1,B1)"}});
//
// Set cell value and rows style for a worksheet with stream writer:
//
// err := sw.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1}},
// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
if err := checkSheetName(sheet); err != nil {
return nil, err
}
sheetID := f.getSheetID(sheet)
if sheetID == -1 {
return nil, ErrSheetNotExist{sheet}
}
sw := &StreamWriter{
file: f,
Sheet: sheet,
SheetID: sheetID,
rawData: bufferedWriter{tmpDir: f.options.TmpDir},
}
var err error
sw.worksheet, err = f.workSheetReader(sheet)
if err != nil {
return nil, err
}
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
if f.streams == nil {
f.streams = make(map[string]*StreamWriter)
}
f.streams[sheetXMLPath] = sw
_, _ = sw.rawData.WriteString(xml.Header + ``, rID)
if err = sw.file.addContentTypePart(tableID, "table"); err != nil {
return err
}
b, _ := xml.Marshal(tbl)
sw.file.saveFileList(tableXML, b)
return err
}
// Extract values from a row in the StreamWriter.
func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) {
res = make([]string, vCol-hCol+1)
r, err := sw.rawData.Reader()
if err != nil {
return nil, err
}
dec := sw.file.xmlNewDecoder(r)
for {
token, err := dec.Token()
if err == io.EOF {
return res, nil
}
if err != nil {
return nil, err
}
startElement, ok := getRowElement(token, hRow)
if !ok {
continue
}
// decode cells
var row xlsxRow
if err := dec.DecodeElement(&row, &startElement); err != nil {
return nil, err
}
for _, c := range row.C {
col, _, err := CellNameToCoordinates(c.R)
if err != nil {
return nil, err
}
if col < hCol || col > vCol {
continue
}
res[col-hCol], _ = c.getValueFrom(sw.file, nil, false)
}
return res, nil
}
}
// Check if the token is an worksheet row with the matching row number.
func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) {
startElement, ok = token.(xml.StartElement)
if !ok {
return
}
ok = startElement.Name.Local == "row"
if !ok {
return
}
ok = false
for _, attr := range startElement.Attr {
if attr.Name.Local != "r" {
continue
}
row, _ := strconv.Atoi(attr.Value)
if row == hRow {
ok = true
return
}
}
return
}
// Cell can be used directly in StreamWriter.SetRow to specify a style and
// a value.
type Cell struct {
StyleID int
Formula string
Value interface{}
}
// RowOpts define the options for the set row, it can be used directly in
// StreamWriter.SetRow to specify the style and properties of the row.
type RowOpts struct {
Height float64
Hidden bool
StyleID int
OutlineLevel int
}
// marshalAttrs prepare attributes of the row.
func (r *RowOpts) marshalAttrs() (strings.Builder, error) {
var (
err error
attrs strings.Builder
)
if r == nil {
return attrs, err
}
if r.Height > MaxRowHeight {
err = ErrMaxRowHeight
return attrs, err
}
if r.OutlineLevel > 7 {
err = ErrOutlineLevel
return attrs, err
}
if r.StyleID > 0 {
attrs.WriteString(` s="`)
attrs.WriteString(strconv.Itoa(r.StyleID))
attrs.WriteString(`" customFormat="1"`)
}
if r.Height > 0 {
attrs.WriteString(` ht="`)
attrs.WriteString(strconv.FormatFloat(r.Height, 'f', -1, 64))
attrs.WriteString(`" customHeight="1"`)
}
if r.OutlineLevel > 0 {
attrs.WriteString(` outlineLevel="`)
attrs.WriteString(strconv.Itoa(r.OutlineLevel))
attrs.WriteString(`"`)
}
if r.Hidden {
attrs.WriteString(` hidden="1"`)
}
return attrs, err
}
// parseRowOpts provides a function to parse the optional settings for
// *StreamWriter.SetRow.
func parseRowOpts(opts ...RowOpts) *RowOpts {
options := &RowOpts{}
for _, opt := range opts {
options = &opt
}
return options
}
// SetRow writes an array to stream rows by giving starting cell reference and a
// pointer to an array of values. Note that you must call the 'Flush' function
// to end the streaming writing process.
//
// As a special case, if Cell is used as a value, then the Cell.StyleID will be
// applied to that cell.
func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpts) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
if row <= sw.rows {
return newStreamSetRowError(row)
}
sw.rows = row
sw.writeSheetData()
options := parseRowOpts(opts...)
attrs, err := options.marshalAttrs()
if err != nil {
return err
}
_, _ = sw.rawData.WriteString(``)
for i, val := range values {
if val == nil {
continue
}
ref, err := CoordinatesToCellName(col+i, row)
if err != nil {
return err
}
c := xlsxC{R: ref, S: sw.worksheet.prepareCellStyle(col+i, row, options.StyleID)}
var s int
if v, ok := val.(Cell); ok {
s, val = v.StyleID, v.Value
setCellFormula(&c, v.Formula)
} else if v, ok := val.(*Cell); ok && v != nil {
s, val = v.StyleID, v.Value
setCellFormula(&c, v.Formula)
}
if s > 0 {
c.S = s
}
if err = sw.setCellValFunc(&c, val); err != nil {
_, _ = sw.rawData.WriteString(`
`)
return err
}
writeCell(&sw.rawData, c)
}
_, _ = sw.rawData.WriteString(`
`)
return sw.rawData.Sync()
}
// SetColVisible provides a function set the visibility of a single column or
// multiple columns for the StreamWriter. Note that you must call the
// 'SetColVisible' function before the 'SetRow' function.
//
// For example hide column D:
//
// err := sw.SetColVisible(4, 4, false)
//
// Hide the columns from D to F (included):
//
// err := sw.SetColVisible(4, 6, false)
func (sw *StreamWriter) SetColVisible(minVal, maxVal int, visible bool) error {
if sw.sheetWritten {
return newStreamSetRowOrderError("SetColVisible")
}
if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns {
return ErrColumnNumber
}
if minVal > maxVal {
minVal, maxVal = maxVal, minVal
}
sw.worksheet.setColVisible(minVal, maxVal, visible)
return nil
}
// SetColOutlineLevel provides a function to set outline level of a single
// column for the StreamWriter. The value of parameter 'level' is 1-7. Note that
// you must call the 'SetColOutlineLevel' function before the 'SetRow' function.
// For example, set outline level of column D to 2:
//
// err := sw.SetColOutlineLevel(4, 2)
func (sw *StreamWriter) SetColOutlineLevel(col int, level uint8) error {
if sw.sheetWritten {
return newStreamSetRowOrderError("SetColOutlineLevel")
}
if col < MinColumns || col > MaxColumns {
return ErrColumnNumber
}
if level > 7 || level < 1 {
return ErrOutlineLevel
}
sw.worksheet.setColOutlineLevel(col, level)
return nil
}
// SetColStyle provides a function to set the style of a single column or
// multiple columns for the StreamWriter. Note that you must call
// the 'SetColStyle' function before the 'SetRow' function. For example set
// style of column H:
//
// err := sw.SetColStyle(8, 8, style)
func (sw *StreamWriter) SetColStyle(minVal, maxVal, styleID int) error {
if sw.sheetWritten {
return newStreamSetRowOrderError("SetColStyle")
}
if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns {
return ErrColumnNumber
}
if maxVal < minVal {
minVal, maxVal = maxVal, minVal
}
s, err := sw.file.stylesReader()
if err != nil {
return err
}
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
return newInvalidStyleID(styleID)
}
sw.worksheet.setColStyle(minVal, maxVal, styleID)
return nil
}
// SetColWidth provides a function to set the width of a single column or
// multiple columns for the StreamWriter. Note that you must call
// the 'SetColWidth' function before the 'SetRow' function. For example set
// the width column B:C as 20:
//
// err := sw.SetColWidth(2, 3, 20)
func (sw *StreamWriter) SetColWidth(minVal, maxVal int, width float64) error {
if sw.sheetWritten {
return newStreamSetRowOrderError("SetColWidth")
}
if minVal < MinColumns || minVal > MaxColumns || maxVal < MinColumns || maxVal > MaxColumns {
return ErrColumnNumber
}
if width > MaxColumnWidth {
return ErrColumnWidth
}
if minVal > maxVal {
minVal, maxVal = maxVal, minVal
}
sw.worksheet.setColWidth(minVal, maxVal, width)
return nil
}
// InsertPageBreak creates a page break to determine where the printed page ends
// and where begins the next one by a given cell reference, the content before
// the page break will be printed on one page and after the page break on
// another.
func (sw *StreamWriter) InsertPageBreak(cell string) error {
return sw.worksheet.insertPageBreak(cell)
}
// SetPanes provides a function to create and remove freeze panes and split
// panes by giving panes options for the StreamWriter. Note that you must call
// the 'SetPanes' function before the 'SetRow' function.
func (sw *StreamWriter) SetPanes(panes *Panes) error {
if sw.sheetWritten {
return newStreamSetRowOrderError("SetPanes")
}
return sw.worksheet.setPanes(panes)
}
// MergeCell provides a function to merge cells by a given range reference for
// the StreamWriter. Don't create a merged cell that overlaps with another
// existing merged cell.
func (sw *StreamWriter) MergeCell(topLeftCell, bottomRightCell string) error {
_, err := cellRefsToCoordinates(topLeftCell, bottomRightCell)
if err != nil {
return err
}
sw.mergeCellsCount++
_, _ = sw.mergeCells.WriteString(``)
return nil
}
// setCellFormula provides a function to set formula of a cell.
func setCellFormula(c *xlsxC, formula string) {
if formula != "" {
c.T, c.F = "str", &xlsxF{Content: formula}
}
}
// setCellTime provides a function to set number of a cell with a time.
func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error {
var date1904, isNum bool
wb, err := sw.file.workbookReader()
if err != nil {
return err
}
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 {
style, _ := sw.file.NewStyle(&Style{NumFmt: 22})
c.S = style
}
return nil
}
// setCellValFunc provides a function to set value of a cell.
func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
var err error
switch val := val.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
setCellIntFunc(c, val)
case float32:
c.setCellFloat(float64(val), -1, 32)
case float64:
c.setCellFloat(val, -1, 64)
case string:
c.setCellValue(val)
case []byte:
c.setCellValue(string(val))
case time.Duration:
c.T, c.V = setCellDuration(val)
case time.Time:
err = sw.setCellTime(c, val)
case bool:
c.T, c.V = setCellBool(val)
case nil:
return err
case []RichTextRun:
c.T, c.IS = "inlineStr", &xlsxSI{}
c.IS.R, err = setRichText(val)
default:
c.setCellValue(fmt.Sprint(val))
}
return err
}
// setCellIntFunc is a wrapper of SetCellInt.
func setCellIntFunc(c *xlsxC, val interface{}) {
switch val := val.(type) {
case int:
c.T, c.V = setCellInt(int64(val))
case int8:
c.T, c.V = setCellInt(int64(val))
case int16:
c.T, c.V = setCellInt(int64(val))
case int32:
c.T, c.V = setCellInt(int64(val))
case int64:
c.T, c.V = setCellInt(val)
case uint:
c.T, c.V = setCellUint(uint64(val))
case uint8:
c.T, c.V = setCellUint(uint64(val))
case uint16:
c.T, c.V = setCellUint(uint64(val))
case uint32:
c.T, c.V = setCellUint(uint64(val))
case uint64:
c.T, c.V = setCellUint(val)
}
}
// writeCell constructs a cell XML and writes it to the buffer.
func writeCell(buf *bufferedWriter, c xlsxC) {
_, _ = buf.WriteString(``)
if c.F != nil {
_, _ = buf.WriteString(``)
_ = xml.EscapeText(buf, []byte(c.F.Content))
_, _ = buf.WriteString(``)
}
if c.V != "" {
_, _ = buf.WriteString(``)
_ = xml.EscapeText(buf, []byte(c.V))
_, _ = buf.WriteString(``)
}
if c.IS != nil {
if len(c.IS.R) > 0 {
is, _ := xml.Marshal(c.IS.R)
_, _ = buf.WriteString(``)
_, _ = buf.Write(is)
_, _ = buf.WriteString(``)
}
if c.IS.T != nil {
_, _ = buf.WriteString(``)
_, _ = buf.Write([]byte(c.IS.T.Val))
_, _ = buf.WriteString(``)
}
}
_, _ = buf.WriteString(``)
}
// writeSheetData prepares the element preceding sheetData and writes the
// sheetData XML start element to the buffer.
func (sw *StreamWriter) writeSheetData() {
if !sw.sheetWritten {
bulkAppendFields(&sw.rawData, sw.worksheet, 5, 6)
if sw.worksheet.Cols != nil {
_, _ = sw.rawData.WriteString("")
for _, col := range sw.worksheet.Cols.Col {
_, _ = sw.rawData.WriteString(` 0 {
_, _ = sw.rawData.WriteString(` outlineLevel="`)
_, _ = sw.rawData.WriteString(strconv.FormatUint(uint64(col.OutlineLevel), 10))
_, _ = sw.rawData.WriteString(`"`)
}
_, _ = sw.rawData.WriteString(`/>`)
}
_, _ = sw.rawData.WriteString("")
}
_, _ = sw.rawData.WriteString(``)
sw.sheetWritten = true
}
}
// Flush ending the streaming writing process.
func (sw *StreamWriter) Flush() error {
sw.writeSheetData()
_, _ = sw.rawData.WriteString(``)
bulkAppendFields(&sw.rawData, sw.worksheet, 9, 16)
mergeCells := strings.Builder{}
if sw.mergeCellsCount > 0 {
_, _ = mergeCells.WriteString(``)
_, _ = mergeCells.WriteString(sw.mergeCells.String())
_, _ = mergeCells.WriteString(``)
}
_, _ = sw.rawData.WriteString(mergeCells.String())
bulkAppendFields(&sw.rawData, sw.worksheet, 18, 39)
_, _ = sw.rawData.WriteString(sw.tableParts)
bulkAppendFields(&sw.rawData, sw.worksheet, 41, 41)
_, _ = sw.rawData.WriteString(``)
if err := sw.rawData.Flush(); err != nil {
return err
}
sheetPath := sw.file.sheetMap[sw.Sheet]
sw.file.Sheet.Delete(sheetPath)
sw.file.checked.Delete(sheetPath)
sw.file.Pkg.Delete(sheetPath)
return nil
}
// bulkAppendFields bulk-appends fields in a worksheet by specified field
// names order range.
func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
s := reflect.ValueOf(ws).Elem()
enc := xml.NewEncoder(w)
for i := 0; i < s.NumField(); i++ {
if from <= i && i <= to {
_ = enc.Encode(s.Field(i).Interface())
}
}
}
// bufferedWriter uses a temp file to store an extended buffer. Writes are
// always made to an in-memory buffer, which will always succeed. The buffer
// is written to the temp file with Sync, which may return an error.
// Therefore, Sync should be periodically called and the error checked.
type bufferedWriter struct {
tmpDir string
tmp *os.File
buf bytes.Buffer
}
// Write to the in-memory buffer. The error is always nil.
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
return bw.buf.Write(p)
}
// WriteString write to the in-memory buffer. The error is always nil.
func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
return bw.buf.WriteString(p)
}
// Reader provides read-access to the underlying buffer/file.
func (bw *bufferedWriter) Reader() (io.Reader, error) {
if bw.tmp == nil {
return bytes.NewReader(bw.buf.Bytes()), nil
}
if err := bw.Flush(); err != nil {
return nil, err
}
fi, err := bw.tmp.Stat()
if err != nil {
return nil, err
}
// os.File.ReadAt does not affect the cursor position and is safe to use here
return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
}
// Sync will write the in-memory buffer to a temp file, if the in-memory
// buffer has grown large enough. Any error will be returned.
func (bw *bufferedWriter) Sync() (err error) {
// Try to use local storage
if bw.buf.Len() < StreamChunkSize {
return nil
}
if bw.tmp == nil {
bw.tmp, err = os.CreateTemp(bw.tmpDir, "excelize-")
if err != nil {
// can not use local storage
return nil
}
}
return bw.Flush()
}
// Flush the entire in-memory buffer to the temp file, if a temp file is being
// used.
func (bw *bufferedWriter) Flush() error {
if bw.tmp == nil {
return nil
}
_, err := bw.buf.WriteTo(bw.tmp)
if err != nil {
return err
}
bw.buf.Reset()
return nil
}
// Close the underlying temp file and reset the in-memory buffer.
func (bw *bufferedWriter) Close() error {
bw.buf.Reset()
if bw.tmp == nil {
return nil
}
defer func() {
_ = os.Remove(bw.tmp.Name())
}()
return bw.tmp.Close()
}
================================================
FILE: stream_test.go
================================================
package excelize
import (
"encoding/xml"
"fmt"
"io"
"math"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func BenchmarkStreamWriter(b *testing.B) {
file := NewFile()
defer func() {
if err := file.Close(); err != nil {
b.Error(err)
}
}()
row := make([]interface{}, 10)
for colID := 0; colID < 10; colID++ {
row[colID] = colID
}
for n := 0; n < b.N; n++ {
streamWriter, _ := file.NewStreamWriter("Sheet1")
for rowID := 10; rowID <= 110; rowID++ {
cell, _ := CoordinatesToCellName(1, rowID)
_ = streamWriter.SetRow(cell, row)
}
}
b.ReportAllocs()
}
func TestStreamWriter(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test max characters in a cell
row := make([]interface{}, 1)
row[0] = strings.Repeat("c", TotalCellChars+2)
assert.NoError(t, streamWriter.SetRow("A1", row))
// Test leading and ending space(s) character characters in a cell
row = make([]interface{}, 1)
row[0] = " characters"
assert.NoError(t, streamWriter.SetRow("A2", row))
row = make([]interface{}, 1)
row[0] = []byte("Word")
assert.NoError(t, streamWriter.SetRow("A3", row))
// Test set cell with style and rich text
styleID, err := file.NewStyle(&Style{Font: &Font{Color: "777777"}})
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A4", []interface{}{
Cell{StyleID: styleID},
Cell{Formula: "SUM(A10,B10)", Value: " preserve space "},
},
RowOpts{Height: 45, StyleID: styleID}))
assert.NoError(t, streamWriter.SetRow("A5", []interface{}{
&Cell{StyleID: styleID, Value: "cell <>&'\""},
&Cell{Formula: "SUM(A10,B10)"},
[]RichTextRun{
{Text: "Rich ", Font: &Font{Color: "2354E8"}},
{Text: "Text", Font: &Font{Color: "E83723"}},
},
}))
assert.NoError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}))
assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID}))
assert.Equal(t, ErrMaxRowHeight, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1}))
assert.NoError(t, streamWriter.SetRow("A9", []interface{}{math.NaN(), math.Inf(0), math.Inf(-1)}))
for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
// Test set cell column overflow
assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber)
assert.NoError(t, file.Close())
// Test close temporary file error
file = NewFile(Options{TmpDir: os.TempDir()})
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
for rowID := 10; rowID <= 25600; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}
assert.NoError(t, streamWriter.rawData.Close())
assert.Error(t, streamWriter.Flush())
streamWriter.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
assert.NoError(t, err)
_, err = streamWriter.rawData.Reader()
assert.NoError(t, err)
assert.NoError(t, streamWriter.rawData.tmp.Close())
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
// Test create stream writer with unsupported charset
file = NewFile()
file.Sheet.Delete("xl/worksheets/sheet1.xml")
file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err = file.NewStreamWriter("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, file.Close())
// Test read cell
file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{Cell{StyleID: styleID, Value: "Data"}}))
assert.NoError(t, streamWriter.Flush())
cellValue, err := file.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "Data", cellValue)
// Test stream reader for a worksheet with huge amounts of data
file, err = OpenFile(filepath.Join("test", "TestStreamWriter.xlsx"))
assert.NoError(t, err)
rows, err := file.Rows("Sheet1")
assert.NoError(t, err)
cells := 0
for rows.Next() {
row, err := rows.Columns()
assert.NoError(t, err)
cells += len(row)
}
assert.NoError(t, rows.Close())
assert.Equal(t, 2559562, cells)
// Save spreadsheet with password.
assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"}))
assert.NoError(t, file.Close())
}
func TestStreamSetColVisible(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColVisible(3, 2, false))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColVisible(0, 3, false))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColVisible(MaxColumns+1, 3, false))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.Equal(t, newStreamSetRowOrderError("SetColVisible"), streamWriter.SetColVisible(2, 3, false))
assert.NoError(t, streamWriter.Flush())
}
func TestStreamSetColOutlineLevel(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColOutlineLevel(4, 2))
assert.Equal(t, ErrOutlineLevel, streamWriter.SetColOutlineLevel(4, 0))
assert.Equal(t, ErrOutlineLevel, streamWriter.SetColOutlineLevel(4, 8))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColOutlineLevel(0, 2))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColOutlineLevel(MaxColumns+1, 2))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.Equal(t, newStreamSetRowOrderError("SetColOutlineLevel"), streamWriter.SetColOutlineLevel(4, 2))
assert.NoError(t, streamWriter.Flush())
}
func TestStreamSetColStyle(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColStyle(3, 2, 0))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColStyle(0, 3, 20))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColStyle(MaxColumns+1, 3, 20))
assert.Equal(t, newInvalidStyleID(2), streamWriter.SetColStyle(1, 3, 2))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.Equal(t, newStreamSetRowOrderError("SetColStyle"), streamWriter.SetColStyle(2, 3, 0))
file = NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
// Test set column style with unsupported charset style sheet
file.Styles = nil
file.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetColStyle(3, 2, 0), "XML syntax error on line 1: invalid UTF-8")
}
func TestStreamSetColWidth(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
styleID, err := file.NewStyle(&Style{
Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1},
})
if err != nil {
fmt.Println(err)
}
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColWidth(3, 2, 20))
assert.NoError(t, streamWriter.SetColStyle(3, 2, styleID))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColWidth(0, 3, 20))
assert.Equal(t, ErrColumnNumber, streamWriter.SetColWidth(MaxColumns+1, 3, 20))
assert.Equal(t, ErrColumnWidth, streamWriter.SetColWidth(1, 3, MaxColumnWidth+1))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.Equal(t, newStreamSetRowOrderError("SetColWidth"), streamWriter.SetColWidth(2, 3, 20))
assert.NoError(t, streamWriter.Flush())
}
func TestStreamSetPanes(t *testing.T) {
file, paneOpts := NewFile(), &Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
}
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetPanes(paneOpts))
assert.Equal(t, ErrParameterInvalid, streamWriter.SetPanes(nil))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.Equal(t, newStreamSetRowOrderError("SetPanes"), streamWriter.SetPanes(paneOpts))
}
func TestStreamTable(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test add table without table header
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 2: unexpected EOF")
// Write some rows. We want enough rows to force a temp file (>16MB)
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
row := []interface{}{1, 2, 3}
for r := 2; r < 10000; r++ {
assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row))
}
// Write a table
assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}))
assert.NoError(t, streamWriter.Flush())
// Verify the table has names
var table xlsxTable
val, ok := file.Pkg.Load("xl/tables/table1.xml")
assert.True(t, ok)
assert.NoError(t, xml.Unmarshal(val.([]byte), &table))
assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name)
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)
assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C1"}))
// Test add table with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.AddTable(&Table{Range: "A:B1"}))
assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), streamWriter.AddTable(&Table{Range: "A1:B"}))
// Test add table with invalid table name
assert.Equal(t, newInvalidNameError("1Table"), streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"}))
// Test add table with row number exceeds maximum limit
assert.Equal(t, ErrMaxRows, streamWriter.AddTable(&Table{Range: "A1048576:C1048576"}))
// Test add table with unsupported charset content types
file.ContentTypes = nil
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 1: invalid UTF-8")
}
func TestStreamMergeCells(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.MergeCell("A1", "D1"))
// Test merge cells with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.MergeCell("A", "D1"))
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamMergeCells.xlsx")))
}
func TestStreamInsertPageBreak(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.InsertPageBreak("A1"))
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamInsertPageBreak.xlsx")))
}
func TestNewStreamWriter(t *testing.T) {
// Test error exceptions
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
_, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
_, err = file.NewStreamWriter("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test new stream write with invalid sheet name
_, err = file.NewStreamWriter("Sheet:1")
assert.Equal(t, ErrSheetNameInvalid, err)
}
func TestStreamMarshalAttrs(t *testing.T) {
var r *RowOpts
attrs, err := r.marshalAttrs()
assert.NoError(t, err)
assert.Empty(t, attrs)
}
func TestStreamSetRow(t *testing.T) {
// Test error exceptions
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), streamWriter.SetRow("A", []interface{}{}))
// Test set row with non-ascending row number
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
assert.Equal(t, newStreamSetRowError(1), streamWriter.SetRow("A1", []interface{}{}))
// Test set row with unsupported charset workbook
file.WorkBook = nil
file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8")
}
func TestStreamSetRowNilValues(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}}))
assert.NoError(t, streamWriter.Flush())
ws, err := file.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NotEqual(t, ws.SheetData.Row[0].C[0].XMLName.Local, "c")
}
func TestStreamSetRowWithStyle(t *testing.T) {
file := NewFile()
defer func() {
assert.NoError(t, file.Close())
}()
grayStyleID, err := file.NewStyle(&Style{Font: &Font{Color: "777777"}})
assert.NoError(t, err)
blueStyleID, err := file.NewStyle(&Style{Font: &Font{Color: "0000FF"}})
assert.NoError(t, err)
sheetName := "Sheet1"
streamWriter, err := file.NewStreamWriter(sheetName)
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColStyle(1, 1, grayStyleID))
assert.NoError(t, streamWriter.SetColStyle(3, 3, blueStyleID))
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{
"A1",
Cell{Value: "B1"},
&Cell{Value: "C1"},
Cell{StyleID: blueStyleID, Value: "D1"},
&Cell{StyleID: blueStyleID, Value: "E1"},
}, RowOpts{StyleID: grayStyleID}))
assert.NoError(t, streamWriter.SetRow("A2", []interface{}{
"A2",
Cell{Value: "B2"},
&Cell{Value: "C2"},
Cell{StyleID: grayStyleID, Value: "D2"},
&Cell{StyleID: blueStyleID, Value: "E2"},
}))
assert.NoError(t, streamWriter.Flush())
ws, err := file.workSheetReader(sheetName)
assert.NoError(t, err)
for colIdx, expected := range []int{grayStyleID, grayStyleID, grayStyleID, blueStyleID, blueStyleID} {
assert.Equal(t, expected, ws.SheetData.Row[0].C[colIdx].S)
}
for colIdx, expected := range []int{grayStyleID, 0, blueStyleID, grayStyleID, blueStyleID} {
assert.Equal(t, expected, ws.SheetData.Row[1].C[colIdx].S)
}
}
func TestStreamSetCellValFunc(t *testing.T) {
f := NewFile()
defer func() {
assert.NoError(t, f.Close())
}()
sw, err := f.NewStreamWriter("Sheet1")
assert.NoError(t, err)
c := &xlsxC{}
for _, val := range []interface{}{
128,
int8(-128),
int16(-32768),
int32(-2147483648),
int64(-9223372036854775808),
uint(128),
uint8(255),
uint16(65535),
uint32(4294967295),
uint64(18446744073709551615),
float32(100.1588),
100.1588,
" Hello",
[]byte(" Hello"),
time.Now().UTC(),
time.Duration(1e13),
true,
nil,
complex64(5 + 10i),
} {
assert.NoError(t, sw.setCellValFunc(c, val))
}
}
func TestStreamWriterOutlineLevel(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
// Test set outlineLevel in row
assert.NoError(t, streamWriter.SetRow("A1", nil, RowOpts{OutlineLevel: 1}))
assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7}))
assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8}))
assert.NoError(t, streamWriter.Flush())
// Save spreadsheet by the given path
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")))
file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))
assert.NoError(t, err)
for rowIdx, expected := range []uint8{1, 7, 0} {
level, err := file.GetRowOutlineLevel("Sheet1", rowIdx+1)
assert.NoError(t, err)
assert.Equal(t, expected, level)
}
assert.NoError(t, file.Close())
}
func TestStreamWriterReader(t *testing.T) {
var (
err error
sw = StreamWriter{
rawData: bufferedWriter{},
}
)
sw.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
assert.NoError(t, err)
assert.NoError(t, sw.rawData.tmp.Close())
// Test reader stat a closed temp file
_, err = sw.rawData.Reader()
assert.Error(t, err)
_, err = sw.getRowValues(1, 1, 1)
assert.Error(t, err)
assert.NoError(t, os.Remove(sw.rawData.tmp.Name()))
sw = StreamWriter{
file: NewFile(),
rawData: bufferedWriter{},
}
// Test getRowValues without expected row
sw.rawData.buf.WriteString("
")
_, err = sw.getRowValues(1, 1, 1)
assert.NoError(t, err)
sw.rawData.buf.Reset()
// Test getRowValues with illegal cell reference
sw.rawData.buf.WriteString("
")
_, err = sw.getRowValues(1, 1, 1)
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
sw.rawData.buf.Reset()
// Test getRowValues with invalid c element characters
sw.rawData.buf.WriteString("
")
_, err = sw.getRowValues(1, 1, 1)
assert.EqualError(t, err, "XML syntax error on line 1: element closed by ")
sw.rawData.buf.Reset()
}
func TestStreamWriterGetRowElement(t *testing.T) {
// Test get row element without r attribute
dec := xml.NewDecoder(strings.NewReader("
"))
for {
token, err := dec.Token()
if err == io.EOF {
break
}
_, ok := getRowElement(token, 0)
assert.False(t, ok)
}
}
================================================
FILE: styles.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"math"
"reflect"
"sort"
"strconv"
"strings"
)
// stylesReader provides a function to get the pointer to the structure after
// deserialization of xl/styles.xml.
func (f *File) stylesReader() (*xlsxStyleSheet, error) {
if f.Styles == nil {
f.Styles = new(xlsxStyleSheet)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))).
Decode(f.Styles); err != nil && err != io.EOF {
return f.Styles, err
}
}
return f.Styles, nil
}
// styleSheetWriter provides a function to save xl/styles.xml after serialize
// structure.
func (f *File) styleSheetWriter() {
if f.Styles != nil {
output, _ := xml.Marshal(f.Styles)
f.saveFileList(defaultXMLPathStyles, f.replaceNameSpaceBytes(defaultXMLPathStyles, output))
}
}
// themeWriter provides a function to save xl/theme/theme1.xml after serialize
// structure.
func (f *File) themeWriter() {
newColor := func(c *decodeCTColor) xlsxCTColor {
return xlsxCTColor{
ScrgbClr: c.ScrgbClr,
SrgbClr: c.SrgbClr,
HslClr: c.HslClr,
SysClr: c.SysClr,
SchemeClr: c.SchemeClr,
PrstClr: c.PrstClr,
}
}
newFontScheme := func(c *decodeFontCollection) xlsxFontCollection {
return xlsxFontCollection{
Latin: c.Latin,
Ea: c.Ea,
Cs: c.Cs,
Font: c.Font,
ExtLst: c.ExtLst,
}
}
if f.Theme != nil {
output, _ := xml.Marshal(xlsxTheme{
XMLNSa: NameSpaceDrawingML.Value,
XMLNSr: SourceRelationship.Value,
Name: f.Theme.Name,
ThemeElements: xlsxBaseStyles{
ClrScheme: xlsxColorScheme{
Name: f.Theme.ThemeElements.ClrScheme.Name,
Dk1: newColor(&f.Theme.ThemeElements.ClrScheme.Dk1),
Lt1: newColor(&f.Theme.ThemeElements.ClrScheme.Lt1),
Dk2: newColor(&f.Theme.ThemeElements.ClrScheme.Dk2),
Lt2: newColor(&f.Theme.ThemeElements.ClrScheme.Lt2),
Accent1: newColor(&f.Theme.ThemeElements.ClrScheme.Accent1),
Accent2: newColor(&f.Theme.ThemeElements.ClrScheme.Accent2),
Accent3: newColor(&f.Theme.ThemeElements.ClrScheme.Accent3),
Accent4: newColor(&f.Theme.ThemeElements.ClrScheme.Accent4),
Accent5: newColor(&f.Theme.ThemeElements.ClrScheme.Accent5),
Accent6: newColor(&f.Theme.ThemeElements.ClrScheme.Accent6),
Hlink: newColor(&f.Theme.ThemeElements.ClrScheme.Hlink),
FolHlink: newColor(&f.Theme.ThemeElements.ClrScheme.FolHlink),
ExtLst: f.Theme.ThemeElements.ClrScheme.ExtLst,
},
FontScheme: xlsxFontScheme{
Name: f.Theme.ThemeElements.FontScheme.Name,
MajorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MajorFont),
MinorFont: newFontScheme(&f.Theme.ThemeElements.FontScheme.MinorFont),
ExtLst: f.Theme.ThemeElements.FontScheme.ExtLst,
},
FmtScheme: xlsxStyleMatrix{
Name: f.Theme.ThemeElements.FmtScheme.Name,
FillStyleLst: f.Theme.ThemeElements.FmtScheme.FillStyleLst,
LnStyleLst: f.Theme.ThemeElements.FmtScheme.LnStyleLst,
EffectStyleLst: f.Theme.ThemeElements.FmtScheme.EffectStyleLst,
BgFillStyleLst: f.Theme.ThemeElements.FmtScheme.BgFillStyleLst,
},
ExtLst: f.Theme.ThemeElements.ExtLst,
},
ObjectDefaults: f.Theme.ObjectDefaults,
ExtraClrSchemeLst: f.Theme.ExtraClrSchemeLst,
CustClrLst: f.Theme.CustClrLst,
ExtLst: f.Theme.ExtLst,
})
f.saveFileList(defaultXMLPathTheme, f.replaceNameSpaceBytes(defaultXMLPathTheme, output))
}
}
// sharedStringsWriter provides a function to save xl/sharedStrings.xml after
// serialize structure.
func (f *File) sharedStringsWriter() {
if f.SharedStrings != nil {
output, _ := xml.Marshal(f.SharedStrings)
f.saveFileList(defaultXMLPathSharedStrings, f.replaceNameSpaceBytes(defaultXMLPathSharedStrings, output))
}
}
// parseFormatStyleSet provides a function to parse the format settings of the
// cells and conditional formats.
func parseFormatStyleSet(style *Style) (*Style, error) {
var err error
if style.Font != nil {
if countUTF16String(style.Font.Family) > MaxFontFamilyLength {
return style, ErrFontLength
}
if style.Font.Size > MaxFontSize {
return style, ErrFontSize
}
}
switch style.Fill.Type {
case "gradient":
if len(style.Fill.Color) != 2 {
return style, ErrFillGradientColor
}
if style.Fill.Shading < 0 || style.Fill.Shading > 16 {
return style, ErrFillGradientShading
}
case "pattern":
if len(style.Fill.Color) > 1 {
return style, ErrFillPatternColor
}
if style.Fill.Pattern < 0 || style.Fill.Pattern > 18 {
return style, ErrFillPattern
}
case "":
default:
return style, ErrFillType
}
if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 {
err = ErrCustomNumFmt
}
return style, err
}
// NewStyle provides a function to create the style for cells by a given style
// options, and returns style index. The same style index can not be used
// across different workbook. This function is concurrency safe. Note that
// the 'Font.Color' field uses an RGB color represented in 'RRGGBB' hexadecimal
// notation.
//
// The following table shows the border types used in 'Border.Type' supported by
// excelize:
//
// Type | Description
// --------------+------------------
// left | Left border
// top | Top border
// right | Right border
// bottom | Bottom border
// diagonalDown | Diagonal down border
// diagonalUp | Diagonal up border
//
// The following table shows the border styles used in 'Border.Style' supported
// by excelize index number:
//
// Index | Name | Weight | Style
// -------+---------------+--------+-------------
// 0 | None | 0 |
// 1 | Continuous | 1 | -----------
// 2 | Continuous | 2 | -----------
// 3 | Dash | 1 | - - - - - -
// 4 | Dot | 1 | . . . . . .
// 5 | Continuous | 3 | -----------
// 6 | Double | 3 | ===========
// 7 | Continuous | 0 | -----------
// 8 | Dash | 2 | - - - - - -
// 9 | Dash Dot | 1 | - . - . - .
// 10 | Dash Dot | 2 | - . - . - .
// 11 | Dash Dot Dot | 1 | - . . - . .
// 12 | Dash Dot Dot | 2 | - . . - . .
// 13 | SlantDash Dot | 2 | / - . / - .
//
// The following table shows the border styles used in 'Border.Style' in the
// order shown in the Excel dialog:
//
// Index | Style | Index | Style
// -------+-------------+-------+-------------
// 0 | None | 12 | - . . - . .
// 7 | ----------- | 13 | / - . / - .
// 4 | . . . . . . | 10 | - . - . - .
// 11 | - . . - . . | 8 | - - - - - -
// 9 | - . - . - . | 2 | -----------
// 3 | - - - - - - | 5 | -----------
// 1 | ----------- | 6 | ===========
//
// The 'Fill.Type' use to set fill type, supported types are 'gradient' or
// 'pattern'. When configuring a fill of type 'gradient', a valid 'Fill.Shading'
// value must be provided, and 'Fill.Color' must be an array containing exactly
// two valid color values. When configuring a fill of type 'pattern', a valid
// 'Fill.Pattern' value must be provided, and 'Fill.Color' must be an array
// containing exactly one valid color value.
//
// The following table shows the shading styles used in 'Fill.Shading' supported
// by excelize index number:
//
// Index | Style | Index | Style
// -------+-----------------+-------+-----------------
// 0-2 | Horizontal | 9-11 | Diagonal down
// 3-5 | Vertical | 12-15 | From corner
// 6-8 | Diagonal Up | 16 | From center
//
// The following table shows the pattern styles used in 'Fill.Pattern' supported
// by excelize index number:
//
// Index | Style | Index | Style
// -------+-----------------+-------+-----------------
// 0 | None | 10 | darkTrellis
// 1 | solid | 11 | lightHorizontal
// 2 | mediumGray | 12 | lightVertical
// 3 | darkGray | 13 | lightDown
// 4 | lightGray | 14 | lightUp
// 5 | darkHorizontal | 15 | lightGrid
// 6 | darkVertical | 16 | lightTrellis
// 7 | darkDown | 17 | gray125
// 8 | darkUp | 18 | gray0625
// 9 | darkGrid | |
//
// The 'Fill.Transparency' only use to set transparency for chart and shape, not
// used for cell. The value of 'Fill.Transparency' should be a number from 0 to
// 100, which represents 0% to 100%, The default value is 0, representing full
// opaque (not transparent).
//
// The 'Alignment.Indent' is an integer value, where an increment of 1
// represents 3 spaces. Indicates the number of spaces (of the normal style
// font) of indentation for text in a cell. The number of spaces to indent is
// calculated as following:
//
// Number of spaces to indent = indent value * 3
//
// For example, an indent value of 1 means that the text begins 3 space widths
// (of the normal style font) from the edge of the cell. Note: The width of one
// space character is defined by the font. Only left, right, and distributed
// horizontal alignments are supported.
//
// The following table shows the type of cells' horizontal alignment used
// in 'Alignment.Horizontal':
//
// Style
// ------------------
// left
// center
// right
// fill
// justify
// centerContinuous
// distributed
//
// The following table shows the type of cells' vertical alignment used in
// 'Alignment.Vertical':
//
// Style
// ------------------
// top
// center
// justify
// distributed
//
// The 'Alignment.ReadingOrder' is an uint64 value indicating whether the
// reading order of the cell is left-to-right, right-to-left, or context
// dependent. the valid value of this field was:
//
// Value | Description
// -------+----------------------------------------------------
// 0 | Context Dependent - reading order is determined by scanning the
// | text for the first non-whitespace character: if it is a strong
// | right-to-left character, the reading order is right-to-left;
// | otherwise, the reading order left-to-right.
// 1 | Left-to-Right: reading order is left-to-right in the cell, as in
// | English.
// 2 | Right-to-Left: reading order is right-to-left in the cell, as in
// | Hebrew.
//
// The 'Alignment.RelativeIndent' is an integer value to indicate the additional
// number of spaces of indentation to adjust for text in a cell.
//
// The following table shows the type of font underline style used in
// 'Font.Underline':
//
// Style
// ------------------
// none
// single
// double
//
// NumFmt is used to set the built-in all languages formats index, built-in
// language formats index, or built-in currency formats index, it doesn't work
// when you specify the custom number format by CustomNumFmt. When you get
// style definition by the GetStyle or GetConditionalStyle function, the NumFmt
// only works if the number format code is exactly equal with any built-in all
// languages format code, built-in language formats code, or built-in currency
// format code.
//
// Excel's built-in all languages formats are shown in the following table:
//
// Index | Format String
// -------+----------------------------------------------------
// 0 | General
// 1 | 0
// 2 | 0.00
// 3 | #,##0
// 4 | #,##0.00
// 5 | ($#,##0_);($#,##0)
// 6 | ($#,##0_);[Red]($#,##0)
// 7 | ($#,##0.00_);($#,##0.00)
// 8 | ($#,##0.00_);[Red]($#,##0.00)
// 9 | 0%
// 10 | 0.00%
// 11 | 0.00E+00
// 12 | # ?/?
// 13 | # ??/??
// 14 | m/d/yy
// 15 | d-mmm-yy
// 16 | d-mmm
// 17 | mmm-yy
// 18 | h:mm AM/PM
// 19 | h:mm:ss AM/PM
// 20 | h:mm
// 21 | h:mm:ss
// 22 | m/d/yy h:mm
// ... | ...
// 37 | (#,##0_);(#,##0)
// 38 | (#,##0_);[Red](#,##0)
// 39 | (#,##0.00_);(#,##0.00)
// 40 | (#,##0.00_);[Red](#,##0.00)
// 41 | _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)
// 42 | _($* #,##0_);_($* (#,##0);_($* "-"_);_(@_)
// 43 | _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)
// 44 | _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)
// 45 | mm:ss
// 46 | [h]:mm:ss
// 47 | mm:ss.0
// 48 | ##0.0E+0
// 49 | @
//
// Number format code in zh-tw language:
//
// Index | Symbol
// -------+-------------------------------------------
// 27 | [$-404]e/m/d
// 28 | [$-404]e"年"m"月"d"日"
// 29 | [$-404]e"年"m"月"d"日"
// 30 | m/d/yy
// 31 | yyyy"年"m"月"d"日"
// 32 | hh"時"mm"分"
// 33 | hh"時"mm"分"ss"秒"
// 34 | 上午/下午 hh"時"mm"分"
// 35 | 上午/下午 hh"時"mm"分"ss"秒"
// 36 | [$-404]e/m/d
// 50 | [$-404]e/m/d
// 51 | [$-404]e"年"m"月"d"日"
// 52 | 上午/下午 hh"時"mm"分"
// 53 | 上午/下午 hh"時"mm"分"ss"秒"
// 54 | [$-404]e"年"m"月"d"日"
// 55 | 上午/下午 hh"時"mm"分"
// 56 | 上午/下午 hh"時"mm"分"ss"秒"
// 57 | [$-404]e/m/d
// 58 | [$-404]e"年"m"月"d"日"
//
// Number format code in zh-cn language:
//
// Index | Symbol
// -------+-------------------------------------------
// 27 | yyyy"年"m"月"
// 28 | m"月"d"日"
// 29 | m"月"d"日"
// 30 | m-d-yy
// 31 | yyyy"年"m"月"d"日"
// 32 | h"时"mm"分"
// 33 | h"时"mm"分"ss"秒"
// 34 | 上午/下午 h"时"mm"分"
// 35 | 上午/下午 h"时"mm"分"ss"秒
// 36 | yyyy"年"m"月
// 50 | yyyy"年"m"月
// 51 | m"月"d"日
// 52 | yyyy"年"m"月
// 53 | m"月"d"日
// 54 | m"月"d"日
// 55 | 上午/下午 h"时"mm"分
// 56 | 上午/下午 h"时"mm"分"ss"秒
// 57 | yyyy"年"m"月
// 58 | m"月"d"日"
//
// Number format code in ja-jp language:
//
// Index | Symbol
// -------+-------------------------------------------
// 27 | [$-411]ge.m.d
// 28 | [$-411]ggge"年"m"月"d"日
// 29 | [$-411]ggge"年"m"月"d"日
// 30 | m/d/y
// 31 | yyyy"年"m"月"d"日
// 32 | h"時"mm"分
// 33 | h"時"mm"分"ss"秒
// 34 | yyyy"年"m"月
// 35 | m"月"d"日
// 36 | [$-411]ge.m.d
// 50 | [$-411]ge.m.d
// 51 | [$-411]ggge"年"m"月"d"日
// 52 | yyyy"年"m"月
// 53 | m"月"d"日
// 54 | [$-411]ggge"年"m"月"d"日
// 55 | yyyy"年"m"月
// 56 | m"月"d"日
// 57 | [$-411]ge.m.d
// 58 | [$-411]ggge"年"m"月"d"日"
//
// Number format code in ko-kr language:
//
// Index | Symbol
// -------+-------------------------------------------
// 27 | yyyy"年" mm"月" dd"日
// 28 | mm-d
// 29 | mm-d
// 30 | mm-dd-y
// 31 | yyyy"년" mm"월" dd"일
// 32 | h"시" mm"분
// 33 | h"시" mm"분" ss"초
// 34 | yyyy-mm-d
// 35 | yyyy-mm-d
// 36 | yyyy"年" mm"月" dd"日
// 50 | yyyy"年" mm"月" dd"日
// 51 | mm-d
// 52 | yyyy-mm-d
// 53 | yyyy-mm-d
// 54 | mm-d
// 55 | yyyy-mm-d
// 56 | yyyy-mm-d
// 57 | yyyy"年" mm"月" dd"日
// 58 | mm-dd
//
// Number format code in th-th language:
//
// Index | Symbol
// -------+-------------------------------------------
// 59 | t
// 60 | t0.0
// 61 | t#,##
// 62 | t#,##0.0
// 67 | t0
// 68 | t0.00
// 69 | t# ?/
// 70 | t# ??/?
// 71 | ว/ด/ปปป
// 72 | ว-ดดด-ป
// 73 | ว-ดด
// 74 | ดดด-ป
// 75 | ช:น
// 76 | ช:นน:ท
// 77 | ว/ด/ปปปป ช:น
// 78 | นน:ท
// 79 | [ช]:นน:ท
// 80 | นน:ทท.
// 81 | d/m/bb
//
// Excelize built-in currency formats are shown in the following table, only
// support these types in the following table (Index number is used only for
// markup and is not used inside an Excel file and you can't get formatted value
// by the function GetCellValue) currently:
//
// Index | Symbol
// -------+---------------------------------------------------------------
// 164 | ¥
// 165 | $ English (United States)
// 166 | $ Cherokee (United States)
// 167 | $ Chinese (Singapore)
// 168 | $ Chinese (Taiwan)
// 169 | $ English (Australia)
// 170 | $ English (Belize)
// 171 | $ English (Canada)
// 172 | $ English (Jamaica)
// 173 | $ English (New Zealand)
// 174 | $ English (Singapore)
// 175 | $ English (Trinidad & Tobago)
// 176 | $ English (U.S. Virgin Islands)
// 177 | $ English (United States)
// 178 | $ French (Canada)
// 179 | $ Hawaiian (United States)
// 180 | $ Malay (Brunei)
// 181 | $ Quechua (Ecuador)
// 182 | $ Spanish (Chile)
// 183 | $ Spanish (Colombia)
// 184 | $ Spanish (Ecuador)
// 185 | $ Spanish (El Salvador)
// 186 | $ Spanish (Mexico)
// 187 | $ Spanish (Puerto Rico)
// 188 | $ Spanish (United States)
// 189 | $ Spanish (Uruguay)
// 190 | £ English (United Kingdom)
// 191 | £ Scottish Gaelic (United Kingdom)
// 192 | £ Welsh (United Kindom)
// 193 | ¥ Chinese (China)
// 194 | ¥ Japanese (Japan)
// 195 | ¥ Sichuan Yi (China)
// 196 | ¥ Tibetan (China)
// 197 | ¥ Uyghur (China)
// 198 | ֏ Armenian (Armenia)
// 199 | ؋ Pashto (Afghanistan)
// 200 | ؋ Persian (Afghanistan)
// 201 | ৳ Bengali (Bangladesh)
// 202 | ៛ Khmer (Cambodia)
// 203 | ₡ Spanish (Costa Rica)
// 204 | ₦ Hausa (Nigeria)
// 205 | ₦ Igbo (Nigeria)
// 206 | ₩ Korean (South Korea)
// 207 | ₪ Hebrew (Israel)
// 208 | ₫ Vietnamese (Vietnam)
// 209 | € Basque (Spain)
// 210 | € Breton (France)
// 211 | € Catalan (Spain)
// 212 | € Corsican (France)
// 213 | € Dutch (Belgium)
// 214 | € Dutch (Netherlands)
// 215 | € English (Ireland)
// 216 | € Estonian (Estonia)
// 217 | € Euro (€ 123)
// 218 | € Euro (123 €)
// 219 | € Finnish (Finland)
// 220 | € French (Belgium)
// 221 | € French (France)
// 222 | € French (Luxembourg)
// 223 | € French (Monaco)
// 224 | € French (Réunion)
// 225 | € Galician (Spain)
// 226 | € German (Austria)
// 227 | € German (German)
// 228 | € German (Luxembourg)
// 229 | € Greek (Greece)
// 230 | € Inari Sami (Finland)
// 231 | € Irish (Ireland)
// 232 | € Italian (Italy)
// 233 | € Latin (Italy)
// 234 | € Latin, Serbian (Montenegro)
// 235 | € Larvian (Latvia)
// 236 | € Lithuanian (Lithuania)
// 237 | € Lower Sorbian (Germany)
// 238 | € Luxembourgish (Luxembourg)
// 239 | € Maltese (Malta)
// 240 | € Northern Sami (Finland)
// 241 | € Occitan (France)
// 242 | € Portuguese (Portugal)
// 243 | € Serbian (Montenegro)
// 244 | € Skolt Sami (Finland)
// 245 | € Slovak (Slovakia)
// 246 | € Slovenian (Slovenia)
// 247 | € Spanish (Spain)
// 248 | € Swedish (Finland)
// 249 | € Swiss German (France)
// 250 | € Upper Sorbian (Germany)
// 251 | € Western Frisian (Netherlands)
// 252 | ₭ Lao (Laos)
// 253 | ₮ Mongolian (Mongolia)
// 254 | ₮ Mongolian, Mongolian (Mongolia)
// 255 | ₱ English (Philippines)
// 256 | ₱ Filipino (Philippines)
// 257 | ₴ Ukrainian (Ukraine)
// 258 | ₸ Kazakh (Kazakhstan)
// 259 | ₹ Arabic, Kashmiri (India)
// 260 | ₹ English (India)
// 261 | ₹ Gujarati (India)
// 262 | ₹ Hindi (India)
// 263 | ₹ Kannada (India)
// 264 | ₹ Kashmiri (India)
// 265 | ₹ Konkani (India)
// 266 | ₹ Manipuri (India)
// 267 | ₹ Marathi (India)
// 268 | ₹ Nepali (India)
// 269 | ₹ Oriya (India)
// 270 | ₹ Punjabi (India)
// 271 | ₹ Sanskrit (India)
// 272 | ₹ Sindhi (India)
// 273 | ₹ Tamil (India)
// 274 | ₹ Urdu (India)
// 275 | ₺ Turkish (Turkey)
// 276 | ₼ Azerbaijani (Azerbaijan)
// 277 | ₼ Cyrillic, Azerbaijani (Azerbaijan)
// 278 | ₽ Russian (Russia)
// 279 | ₽ Sakha (Russia)
// 280 | ₾ Georgian (Georgia)
// 281 | B/. Spanish (Panama)
// 282 | Br Oromo (Ethiopia)
// 283 | Br Somali (Ethiopia)
// 284 | Br Tigrinya (Ethiopia)
// 285 | Bs Quechua (Bolivia)
// 286 | Bs Spanish (Bolivia)
// 287 | BS. Spanish (Venezuela)
// 288 | BWP Tswana (Botswana)
// 289 | C$ Spanish (Nicaragua)
// 290 | CA$ Latin, Inuktitut (Canada)
// 291 | CA$ Mohawk (Canada)
// 292 | CA$ Unified Canadian Aboriginal Syllabics, Inuktitut (Canada)
// 293 | CFA French (Mali)
// 294 | CFA French (Senegal)
// 295 | CFA Fulah (Senegal)
// 296 | CFA Wolof (Senegal)
// 297 | CHF French (Switzerland)
// 298 | CHF German (Liechtenstein)
// 299 | CHF German (Switzerland)
// 300 | CHF Italian (Switzerland)
// 301 | CHF Romansh (Switzerland)
// 302 | CLP Mapuche (Chile)
// 303 | CN¥ Mongolian, Mongolian (China)
// 304 | DZD Central Atlas Tamazight (Algeria)
// 305 | FCFA French (Cameroon)
// 306 | Ft Hungarian (Hungary)
// 307 | G French (Haiti)
// 308 | Gs. Spanish (Paraguay)
// 309 | GTQ K'iche' (Guatemala)
// 310 | HK$ Chinese (Hong Kong (China))
// 311 | HK$ English (Hong Kong (China))
// 312 | HRK Croatian (Croatia)
// 313 | IDR English (Indonesia)
// 314 | IQD Arbic, Central Kurdish (Iraq)
// 315 | ISK Icelandic (Iceland)
// 316 | K Burmese (Myanmar (Burma))
// 317 | Kč Czech (Czech Republic)
// 318 | KM Bosnian (Bosnia & Herzegovina)
// 319 | KM Croatian (Bosnia & Herzegovina)
// 320 | KM Latin, Serbian (Bosnia & Herzegovina)
// 321 | kr Faroese (Faroe Islands)
// 322 | kr Northern Sami (Norway)
// 323 | kr Northern Sami (Sweden)
// 324 | kr Norwegian Bokmål (Norway)
// 325 | kr Norwegian Nynorsk (Norway)
// 326 | kr Swedish (Sweden)
// 327 | kr. Danish (Denmark)
// 328 | kr. Kalaallisut (Greenland)
// 329 | Ksh Swahili (kenya)
// 330 | L Romanian (Moldova)
// 331 | L Russian (Moldova)
// 332 | L Spanish (Honduras)
// 333 | Lekë Albanian (Albania)
// 334 | MAD Arabic, Central Atlas Tamazight (Morocco)
// 335 | MAD French (Morocco)
// 336 | MAD Tifinagh, Central Atlas Tamazight (Morocco)
// 337 | MOP$ Chinese (Macau (China))
// 338 | MVR Divehi (Maldives)
// 339 | Nfk Tigrinya (Eritrea)
// 340 | NGN Bini (Nigeria)
// 341 | NGN Fulah (Nigeria)
// 342 | NGN Ibibio (Nigeria)
// 343 | NGN Kanuri (Nigeria)
// 344 | NOK Lule Sami (Norway)
// 345 | NOK Southern Sami (Norway)
// 346 | NZ$ Maori (New Zealand)
// 347 | PKR Sindhi (Pakistan)
// 348 | PYG Guarani (Paraguay)
// 349 | Q Spanish (Guatemala)
// 350 | R Afrikaans (South Africa)
// 351 | R English (South Africa)
// 352 | R Zulu (South Africa)
// 353 | R$ Portuguese (Brazil)
// 354 | RD$ Spanish (Dominican Republic)
// 355 | RF Kinyarwanda (Rwanda)
// 356 | RM English (Malaysia)
// 357 | RM Malay (Malaysia)
// 358 | RON Romanian (Romania)
// 359 | Rp Indonesoan (Indonesia)
// 360 | Rs Urdu (Pakistan)
// 361 | Rs. Tamil (Sri Lanka)
// 362 | RSD Latin, Serbian (Serbia)
// 363 | RSD Serbian (Serbia)
// 364 | RUB Bashkir (Russia)
// 365 | RUB Tatar (Russia)
// 366 | S/. Quechua (Peru)
// 367 | S/. Spanish (Peru)
// 368 | SEK Lule Sami (Sweden)
// 369 | SEK Southern Sami (Sweden)
// 370 | soʻm Latin, Uzbek (Uzbekistan)
// 371 | soʻm Uzbek (Uzbekistan)
// 372 | SYP Syriac (Syria)
// 373 | THB Thai (Thailand)
// 374 | TMT Turkmen (Turkmenistan)
// 375 | US$ English (Zimbabwe)
// 376 | ZAR Northern Sotho (South Africa)
// 377 | ZAR Southern Sotho (South Africa)
// 378 | ZAR Tsonga (South Africa)
// 379 | ZAR Tswana (south Africa)
// 380 | ZAR Venda (South Africa)
// 381 | ZAR Xhosa (South Africa)
// 382 | zł Polish (Poland)
// 383 | ден Macedonian (Macedonia)
// 384 | KM Cyrillic, Bosnian (Bosnia & Herzegovina)
// 385 | KM Serbian (Bosnia & Herzegovina)
// 386 | лв. Bulgarian (Bulgaria)
// 387 | p. Belarusian (Belarus)
// 388 | сом Kyrgyz (Kyrgyzstan)
// 389 | сом Tajik (Tajikistan)
// 390 | ج.م. Arabic (Egypt)
// 391 | د.أ. Arabic (Jordan)
// 392 | د.أ. Arabic (United Arab Emirates)
// 393 | د.ب. Arabic (Bahrain)
// 394 | د.ت. Arabic (Tunisia)
// 395 | د.ج. Arabic (Algeria)
// 396 | د.ع. Arabic (Iraq)
// 397 | د.ك. Arabic (Kuwait)
// 398 | د.ل. Arabic (Libya)
// 399 | د.م. Arabic (Morocco)
// 400 | ر Punjabi (Pakistan)
// 401 | ر.س. Arabic (Saudi Arabia)
// 402 | ر.ع. Arabic (Oman)
// 403 | ر.ق. Arabic (Qatar)
// 404 | ر.ي. Arabic (Yemen)
// 405 | ریال Persian (Iran)
// 406 | ل.س. Arabic (Syria)
// 407 | ل.ل. Arabic (Lebanon)
// 408 | ብር Amharic (Ethiopia)
// 409 | रू Nepaol (Nepal)
// 410 | රු. Sinhala (Sri Lanka)
// 411 | ADP
// 412 | AED
// 413 | AFA
// 414 | AFN
// 415 | ALL
// 416 | AMD
// 417 | ANG
// 418 | AOA
// 419 | ARS
// 420 | ATS
// 421 | AUD
// 422 | AWG
// 423 | AZM
// 424 | AZN
// 425 | BAM
// 426 | BBD
// 427 | BDT
// 428 | BEF
// 429 | BGL
// 430 | BGN
// 431 | BHD
// 432 | BIF
// 433 | BMD
// 434 | BND
// 435 | BOB
// 436 | BOV
// 437 | BRL
// 438 | BSD
// 439 | BTN
// 440 | BWP
// 441 | BYR
// 442 | BZD
// 443 | CAD
// 444 | CDF
// 445 | CHE
// 446 | CHF
// 447 | CHW
// 448 | CLF
// 449 | CLP
// 450 | CNY
// 451 | COP
// 452 | COU
// 453 | CRC
// 454 | CSD
// 455 | CUC
// 456 | CVE
// 457 | CYP
// 458 | CZK
// 459 | DEM
// 460 | DJF
// 461 | DKK
// 462 | DOP
// 463 | DZD
// 464 | ECS
// 465 | ECV
// 466 | EEK
// 467 | EGP
// 468 | ERN
// 469 | ESP
// 470 | ETB
// 471 | EUR
// 472 | FIM
// 473 | FJD
// 474 | FKP
// 475 | FRF
// 476 | GBP
// 477 | GEL
// 478 | GHC
// 479 | GHS
// 480 | GIP
// 481 | GMD
// 482 | GNF
// 483 | GRD
// 484 | GTQ
// 485 | GYD
// 486 | HKD
// 487 | HNL
// 488 | HRK
// 489 | HTG
// 490 | HUF
// 491 | IDR
// 492 | IEP
// 493 | ILS
// 494 | INR
// 495 | IQD
// 496 | IRR
// 497 | ISK
// 498 | ITL
// 499 | JMD
// 500 | JOD
// 501 | JPY
// 502 | KAF
// 503 | KES
// 504 | KGS
// 505 | KHR
// 506 | KMF
// 507 | KPW
// 508 | KRW
// 509 | KWD
// 510 | KYD
// 511 | KZT
// 512 | LAK
// 513 | LBP
// 514 | LKR
// 515 | LRD
// 516 | LSL
// 517 | LTL
// 518 | LUF
// 519 | LVL
// 520 | LYD
// 521 | MAD
// 522 | MDL
// 523 | MGA
// 524 | MGF
// 525 | MKD
// 526 | MMK
// 527 | MNT
// 528 | MOP
// 529 | MRO
// 530 | MTL
// 531 | MUR
// 532 | MVR
// 533 | MWK
// 534 | MXN
// 535 | MXV
// 536 | MYR
// 537 | MZM
// 538 | MZN
// 539 | NAD
// 540 | NGN
// 541 | NIO
// 542 | NLG
// 543 | NOK
// 544 | NPR
// 545 | NTD
// 546 | NZD
// 547 | OMR
// 548 | PAB
// 549 | PEN
// 550 | PGK
// 551 | PHP
// 552 | PKR
// 553 | PLN
// 554 | PTE
// 555 | PYG
// 556 | QAR
// 557 | ROL
// 558 | RON
// 559 | RSD
// 560 | RUB
// 561 | RUR
// 562 | RWF
// 563 | SAR
// 564 | SBD
// 565 | SCR
// 566 | SDD
// 567 | SDG
// 568 | SDP
// 569 | SEK
// 570 | SGD
// 571 | SHP
// 572 | SIT
// 573 | SKK
// 574 | SLL
// 575 | SOS
// 576 | SPL
// 577 | SRD
// 578 | SRG
// 579 | STD
// 580 | SVC
// 581 | SYP
// 582 | SZL
// 583 | THB
// 584 | TJR
// 585 | TJS
// 586 | TMM
// 587 | TMT
// 588 | TND
// 589 | TOP
// 590 | TRL
// 591 | TRY
// 592 | TTD
// 593 | TWD
// 594 | TZS
// 595 | UAH
// 596 | UGX
// 597 | USD
// 598 | USN
// 599 | USS
// 600 | UYI
// 601 | UYU
// 602 | UZS
// 603 | VEB
// 604 | VEF
// 605 | VND
// 606 | VUV
// 607 | WST
// 608 | XAF
// 609 | XAG
// 610 | XAU
// 611 | XB5
// 612 | XBA
// 613 | XBB
// 614 | XBC
// 615 | XBD
// 616 | XCD
// 617 | XDR
// 618 | XFO
// 619 | XFU
// 620 | XOF
// 621 | XPD
// 622 | XPF
// 623 | XPT
// 624 | XTS
// 625 | XXX
// 626 | YER
// 627 | YUM
// 628 | ZAR
// 629 | ZMK
// 630 | ZMW
// 631 | ZWD
// 632 | ZWL
// 633 | ZWN
// 634 | ZWR
//
// Excelize support set custom number format for cell by CustomNumFmt field. For
// example, set number as date type in Uruguay (Spanish) format for Sheet1!A6:
//
// f := excelize.NewFile()
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// if err := f.SetCellValue("Sheet1", "A6", 42920.5); err != nil {
// fmt.Println(err)
// return
// }
// exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"
// style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp})
// if err != nil {
// fmt.Println(err)
// return
// }
// err = f.SetCellStyle("Sheet1", "A6", "A6", style)
//
// Cell Sheet1!A6 in the spreadsheet application: martes, 04 de Julio de 2017
//
// DecimalPlaces is used to set the decimal places for built-in currency
// formats, it doesn't work if you have specified the built-in all languages
// formats or built-in language formats by NumFmt field, or specify the custom
// number format by CustomNumFmt. When you get style definition by the GetStyle
// or GetConditionalStyle function, the DecimalPlaces only doesn't nil if a
// number format code has the same decimal places in the positive part negative
// part, or only the positive part.
func (f *File) NewStyle(style *Style) (int, error) {
var (
fs *Style
err error
cellXfsID, fontID, borderID, fillID int
)
if style == nil {
return cellXfsID, err
}
fs, err = parseFormatStyleSet(style)
if err != nil {
return cellXfsID, err
}
if fs.DecimalPlaces != nil && (*fs.DecimalPlaces < 0 || *fs.DecimalPlaces > 30) {
fs.DecimalPlaces = intPtr(2)
}
f.mu.Lock()
s, err := f.stylesReader()
if err != nil {
f.mu.Unlock()
return cellXfsID, err
}
f.mu.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
// check given style already exist.
if cellXfsID, err = f.getStyleID(s, fs); err != nil || cellXfsID != -1 {
return cellXfsID, err
}
numFmtID := newNumFmt(s, fs)
if fs.Font != nil {
fontID = f.getFontID(s, fs)
if fontID == -1 {
s.Fonts.Count++
s.Fonts.Font = append(s.Fonts.Font, fs.Font.newFont())
fontID = s.Fonts.Count - 1
}
}
borderID = getBorderID(s, fs)
if borderID == -1 {
if len(fs.Border) == 0 {
borderID = 0
} else {
s.Borders.Count++
s.Borders.Border = append(s.Borders.Border, newBorders(fs))
borderID = s.Borders.Count - 1
}
}
if fillID = getFillID(s, fs); fillID == -1 {
if fill := newFills(fs, true); fill != nil {
s.Fills.Count++
s.Fills.Fill = append(s.Fills.Fill, fill)
fillID = s.Fills.Count - 1
} else {
fillID = 0
}
}
applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs)
applyProtection, protection := fs.Protection != nil, newProtection(fs)
return setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection)
}
var (
// styleBorders list all types of the cell border style.
styleBorders = []string{
"none",
"thin",
"medium",
"dashed",
"dotted",
"thick",
"double",
"hair",
"mediumDashed",
"dashDot",
"mediumDashDot",
"dashDotDot",
"mediumDashDotDot",
"slantDashDot",
}
// styleBorderTypes list all types of the cell border.
styleBorderTypes = []string{
"left", "right", "top", "bottom", "diagonalUp", "diagonalDown",
}
// styleFillPatterns list all types of the cell fill style.
styleFillPatterns = []string{
"none",
"solid",
"mediumGray",
"darkGray",
"lightGray",
"darkHorizontal",
"darkVertical",
"darkDown",
"darkUp",
"darkGrid",
"darkTrellis",
"lightHorizontal",
"lightVertical",
"lightDown",
"lightUp",
"lightGrid",
"lightTrellis",
"gray125",
"gray0625",
}
// styleFillVariants list all preset variants of the fill style.
styleFillVariants = func() []xlsxGradientFill {
return []xlsxGradientFill{
{Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 270, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 180, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
{Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 255, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
{Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 315, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
{Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path"},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Left: 1, Right: 1},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Top: 1},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Left: 1, Right: 1, Top: 1},
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 0.5, Left: 0.5, Right: 0.5, Top: 0.5},
}
}
// getXfIDFuncs provides a function to get xfID by given style.
getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{
"numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool {
if style.CustomNumFmt == nil && numFmtID == -1 {
return xf.NumFmtID != nil && *xf.NumFmtID == 0
}
if style.NegRed || (style.DecimalPlaces != nil && *style.DecimalPlaces != 2) {
return false
}
return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID
},
"font": func(fontID int, xf xlsxXf, style *Style) bool {
if style.Font == nil || fontID == 0 {
return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont)
}
return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont
},
"fill": func(fillID int, xf xlsxXf, style *Style) bool {
if style.Fill.Type == "" || fillID == 0 {
return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill)
}
return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill
},
"border": func(borderID int, xf xlsxXf, style *Style) bool {
if len(style.Border) == 0 {
return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder)
}
return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder
},
"alignment": func(ID int, xf xlsxXf, style *Style) bool {
if style.Alignment == nil {
return xf.ApplyAlignment == nil || !*xf.ApplyAlignment
}
return reflect.DeepEqual(xf.Alignment, newAlignment(style))
},
"protection": func(ID int, xf xlsxXf, style *Style) bool {
if style.Protection == nil {
return xf.ApplyProtection == nil || !*xf.ApplyProtection
}
return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection
},
}
// extractStyleCondFuncs provides a function set to returns if should be
// extract style definition by given style.
extractStyleCondFuncs = map[string]func(xlsxXf, *xlsxStyleSheet) bool{
"fill": func(xf xlsxXf, s *xlsxStyleSheet) bool {
return (xf.ApplyFill == nil || (xf.ApplyFill != nil && *xf.ApplyFill)) &&
xf.FillID != nil && s.Fills != nil &&
*xf.FillID < len(s.Fills.Fill)
},
"border": func(xf xlsxXf, s *xlsxStyleSheet) bool {
return (xf.ApplyBorder == nil || (xf.ApplyBorder != nil && *xf.ApplyBorder)) &&
xf.BorderID != nil && s.Borders != nil &&
*xf.BorderID < len(s.Borders.Border)
},
"font": func(xf xlsxXf, s *xlsxStyleSheet) bool {
return (xf.ApplyFont == nil || (xf.ApplyFont != nil && *xf.ApplyFont)) &&
xf.FontID != nil && s.Fonts != nil &&
*xf.FontID < len(s.Fonts.Font)
},
"alignment": func(xf xlsxXf, s *xlsxStyleSheet) bool {
return xf.ApplyAlignment == nil || (xf.ApplyAlignment != nil && *xf.ApplyAlignment)
},
"protection": func(xf xlsxXf, s *xlsxStyleSheet) bool {
return xf.ApplyProtection == nil || (xf.ApplyProtection != nil && *xf.ApplyProtection)
},
}
// drawContFmtFunc defines functions to create conditional formats.
drawContFmtFunc = map[string]func(p int, ct, ref, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){
"cellIs": drawCondFmtCellIs,
"timePeriod": drawCondFmtTimePeriod,
"text": drawCondFmtText,
"top10": drawCondFmtTop10,
"aboveAverage": drawCondFmtAboveAverage,
"duplicateValues": drawCondFmtDuplicateUniqueValues,
"uniqueValues": drawCondFmtDuplicateUniqueValues,
"containsBlanks": drawCondFmtBlanks,
"notContainsBlanks": drawCondFmtNoBlanks,
"containsErrors": drawCondFmtErrors,
"notContainsErrors": drawCondFmtNoErrors,
"2_color_scale": drawCondFmtColorScale,
"3_color_scale": drawCondFmtColorScale,
"dataBar": drawCondFmtDataBar,
"expression": drawCondFmtExp,
"iconSet": drawCondFmtIconSet,
}
// extractContFmtFunc defines functions to get conditional formats.
extractContFmtFunc = map[string]func(*File, string, *xlsxCfRule, *xlsxExtLst) ConditionalFormatOptions{
"cellIs": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtCellIs(c, extLst)
},
"timePeriod": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtTimePeriod(c, ref, extLst)
},
"containsText": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtText(c, extLst)
},
"notContainsText": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtText(c, extLst)
},
"beginsWith": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtText(c, extLst)
},
"endsWith": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtText(c, extLst)
},
"top10": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtTop10(c, extLst)
},
"aboveAverage": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtAboveAverage(c, extLst)
},
"duplicateValues": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtDuplicateUniqueValues(c, extLst)
},
"uniqueValues": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtDuplicateUniqueValues(c, extLst)
},
"containsBlanks": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtBlanks(c, extLst)
},
"notContainsBlanks": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtNoBlanks(c, extLst)
},
"containsErrors": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtErrors(c, extLst)
},
"notContainsErrors": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtNoErrors(c, extLst)
},
"colorScale": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtColorScale(c, extLst)
},
"dataBar": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtDataBar(c, extLst)
},
"expression": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtExp(c, extLst)
},
"iconSet": func(f *File, ref string, c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return f.extractCondFmtIconSet(c, extLst)
},
}
// validType defined the list of valid validation types.
validType = map[string]string{
"cell": "cellIs",
"average": "aboveAverage",
"duplicate": "duplicateValues",
"unique": "uniqueValues",
"top": "top10",
"bottom": "top10",
"text": "text",
"time_period": "timePeriod",
"blanks": "containsBlanks",
"no_blanks": "notContainsBlanks",
"errors": "containsErrors",
"no_errors": "notContainsErrors",
"2_color_scale": "2_color_scale",
"3_color_scale": "3_color_scale",
"data_bar": "dataBar",
"formula": "expression",
"icon_set": "iconSet",
}
// criteriaType defined the list of valid criteria types.
criteriaType = map[string]string{
"!=": "notEqual",
"<": "lessThan",
"<=": "lessThanOrEqual",
"<>": "notEqual",
"=": "equal",
"==": "equal",
">": "greaterThan",
">=": "greaterThanOrEqual",
"begins with": "beginsWith",
"between": "between",
"containing": "containsText",
"continue month": "nextMonth",
"continue week": "nextWeek",
"ends with": "endsWith",
"equal to": "equal",
"greater than or equal to": "greaterThanOrEqual",
"greater than": "greaterThan",
"last 7 days": "last7Days",
"last month": "lastMonth",
"last week": "lastWeek",
"less than or equal to": "lessThanOrEqual",
"less than": "lessThan",
"not between": "notBetween",
"not containing": "notContains",
"not equal to": "notEqual",
"this month": "thisMonth",
"this week": "thisWeek",
"today": "today",
"tomorrow": "tomorrow",
"yesterday": "yesterday",
}
// operatorType defined the list of valid operator types.
operatorType = map[string]string{
"beginsWith": "begins with",
"between": "between",
"containsText": "containing",
"endsWith": "ends with",
"equal": "equal to",
"greaterThan": "greater than",
"greaterThanOrEqual": "greater than or equal to",
"last7Days": "last 7 days",
"lastMonth": "last month",
"lastWeek": "last week",
"lessThan": "less than",
"lessThanOrEqual": "less than or equal to",
"nextMonth": "continue month",
"nextWeek": "continue week",
"notBetween": "not between",
"notContains": "not containing",
"notEqual": "not equal to",
"thisMonth": "this month",
"thisWeek": "this week",
"today": "today",
"tomorrow": "tomorrow",
"yesterday": "yesterday",
}
// cellIsCriteriaType defined the list of valid criteria types used for
// cellIs conditional formats.
cellIsCriteriaType = []string{
"equal",
"notEqual",
"greaterThan",
"lessThan",
"greaterThanOrEqual",
"lessThanOrEqual",
"containsText",
"notContains",
"beginsWith",
"endsWith",
}
// cfvo3 defined the icon set conditional formatting rules.
cfvo3 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "33"},
{Type: "percent", Val: "67"},
}}}
// cfvo4 defined the icon set conditional formatting rules.
cfvo4 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "25"},
{Type: "percent", Val: "50"},
{Type: "percent", Val: "75"},
}}}
// cfvo5 defined the icon set conditional formatting rules.
cfvo5 = &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "20"},
{Type: "percent", Val: "40"},
{Type: "percent", Val: "60"},
{Type: "percent", Val: "80"},
}}}
// condFmtIconSetPresets defined the list of icon set conditional formatting
// rules.
condFmtIconSetPresets = map[string]*xlsxCfRule{
"3Arrows": cfvo3,
"3ArrowsGray": cfvo3,
"3Flags": cfvo3,
"3Signs": cfvo3,
"3Symbols": cfvo3,
"3Symbols2": cfvo3,
"3TrafficLights1": cfvo3,
"3TrafficLights2": cfvo3,
"4Arrows": cfvo4,
"4ArrowsGray": cfvo4,
"4Rating": cfvo4,
"4RedToBlack": cfvo4,
"4TrafficLights": cfvo4,
"5Arrows": cfvo5,
"5ArrowsGray": cfvo5,
"5Quarters": cfvo5,
"5Rating": cfvo5,
}
// cfvo3 defined the icon set conditional formatting rules.
x14cfvo3 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
{Type: "percent", F: "0"},
{Type: "percent", F: "33"},
{Type: "percent", F: "67"},
}}}
// cfvo5 defined the icon set conditional formatting rules.
x14cfvo5 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
{Type: "percent", F: "0"},
{Type: "percent", F: "20"},
{Type: "percent", F: "40"},
{Type: "percent", F: "60"},
{Type: "percent", F: "80"},
}}}
// condFmtX14IconSetPresets defined the list of icon set conditional
// formatting rules which defined in worksheet extension list.
condFmtX14IconSetPresets = map[string]*xlsxX14CfRule{
"3Stars": x14cfvo3,
"3Triangles": x14cfvo3,
"5Boxes": x14cfvo5,
}
)
// colorChoice returns a hex color code from the actual color values.
func (clr *decodeCTColor) colorChoice() *string {
if clr.SrgbClr != nil {
return clr.SrgbClr.Val
}
if clr.SysClr != nil {
return &clr.SysClr.LastClr
}
return nil
}
// GetBaseColor returns the preferred hex color code by giving hex color code,
// indexed color, and theme color.
func (f *File) GetBaseColor(hexColor string, indexedColor int, themeColor *int) string {
if f.Theme != nil && themeColor != nil {
clrScheme := f.Theme.ThemeElements.ClrScheme
if val, ok := map[int]*string{
0: clrScheme.Lt1.colorChoice(),
1: clrScheme.Dk1.colorChoice(),
2: clrScheme.Lt2.colorChoice(),
3: clrScheme.Dk2.colorChoice(),
4: clrScheme.Accent1.colorChoice(),
5: clrScheme.Accent2.colorChoice(),
6: clrScheme.Accent3.colorChoice(),
7: clrScheme.Accent4.colorChoice(),
8: clrScheme.Accent5.colorChoice(),
9: clrScheme.Accent6.colorChoice(),
}[*themeColor]; ok && val != nil {
return *val
}
}
if len(hexColor) == 6 {
return hexColor
}
if len(hexColor) == 8 {
return strings.TrimPrefix(hexColor, "FF")
}
if f.Styles != nil && f.Styles.Colors != nil && f.Styles.Colors.IndexedColors != nil &&
indexedColor < len(f.Styles.Colors.IndexedColors.RgbColor) {
return strings.TrimPrefix(f.Styles.Colors.IndexedColors.RgbColor[indexedColor].RGB, "FF")
}
if indexedColor < len(IndexedColorMapping) {
return IndexedColorMapping[indexedColor]
}
return hexColor
}
// getThemeColor provides a function to convert theme color or index color to
// RGB color.
func (f *File) getThemeColor(clr *xlsxColor) string {
var RGB string
if clr == nil || f.Theme == nil {
return RGB
}
if RGB = f.GetBaseColor(clr.RGB, clr.Indexed, clr.Theme); RGB != "" {
RGB = strings.TrimPrefix(ThemeColor(RGB, clr.Tint), "FF")
}
return RGB
}
// extractBorders provides a function to extract borders styles settings by
// given border styles definition.
func (f *File) extractBorders(bdr *xlsxBorder, s *xlsxStyleSheet, style *Style) {
if bdr != nil {
var borders []Border
extractBorder := func(lineType string, line *xlsxLine) {
if line != nil && line.Style != "" {
borders = append(borders, Border{
Type: lineType,
Color: f.getThemeColor(line.Color),
Style: inStrSlice(styleBorders, line.Style, false),
})
}
}
for i, line := range []*xlsxLine{
bdr.Left, bdr.Right, bdr.Top, bdr.Bottom, bdr.Diagonal, bdr.Diagonal,
} {
if i < 4 {
extractBorder(styleBorderTypes[i], line)
}
if i == 4 && bdr.DiagonalUp {
extractBorder(styleBorderTypes[i], line)
}
if i == 5 && bdr.DiagonalDown {
extractBorder(styleBorderTypes[i], line)
}
}
style.Border = borders
}
}
// extractFills provides a function to extract fill styles settings by
// given fill styles definition.
func (f *File) extractFills(fl *xlsxFill, s *xlsxStyleSheet, style *Style) {
if fl != nil {
var fill Fill
if fl.GradientFill != nil {
fill.Type = "gradient"
for shading, variants := range styleFillVariants() {
if fl.GradientFill.Bottom == variants.Bottom &&
fl.GradientFill.Degree == variants.Degree &&
fl.GradientFill.Left == variants.Left &&
fl.GradientFill.Right == variants.Right &&
fl.GradientFill.Top == variants.Top &&
fl.GradientFill.Type == variants.Type {
fill.Shading = shading
break
}
}
for _, stop := range fl.GradientFill.Stop {
fill.Color = append(fill.Color, f.getThemeColor(&stop.Color))
}
}
if fl.PatternFill != nil {
fill.Type = "pattern"
fill.Pattern = inStrSlice(styleFillPatterns, fl.PatternFill.PatternType, false)
if fl.PatternFill.BgColor != nil {
fill.Color = []string{f.getThemeColor(fl.PatternFill.BgColor)}
}
if fl.PatternFill.FgColor != nil {
fill.Color = []string{f.getThemeColor(fl.PatternFill.FgColor)}
}
}
style.Fill = fill
}
}
// extractFont provides a function to extract font styles settings by given
// font styles definition.
func extractFont(fnt *xlsxFont) *Font {
if fnt == nil {
return nil
}
var font Font
if fnt.Charset != nil {
font.Charset = fnt.Charset.Val
}
font.Bold = fnt.B.Value()
font.Italic = fnt.I.Value()
if fnt.U != nil {
if font.Underline = fnt.U.Value(); font.Underline == "" {
font.Underline = "single"
}
}
font.Family = fnt.Name.Value()
font.Size = fnt.Sz.Value()
font.Strike = fnt.Strike.Value()
if fnt.Color != nil {
font.Color = strings.TrimPrefix(fnt.Color.RGB, "FF")
font.ColorIndexed = fnt.Color.Indexed
font.ColorTheme = fnt.Color.Theme
font.ColorTint = fnt.Color.Tint
}
font.VertAlign = fnt.VertAlign.Value()
return &font
}
// extractNumFmt provides a function to extract number format by given styles
// definition.
func (f *File) extractNumFmt(n *int, s *xlsxStyleSheet, style *Style) {
if n != nil {
numFmtID := *n
if builtInFmtCode, ok := builtInNumFmt[numFmtID]; ok || isLangNumFmt(numFmtID) {
style.NumFmt = numFmtID
if decimalPlaces := f.extractNumFmtDecimal(builtInFmtCode); decimalPlaces != -1 {
style.DecimalPlaces = &decimalPlaces
}
return
}
if s.NumFmts != nil {
for _, numFmt := range s.NumFmts.NumFmt {
if numFmt.NumFmtID != numFmtID {
continue
}
if decimalPlaces := f.extractNumFmtDecimal(numFmt.FormatCode); decimalPlaces != -1 {
style.DecimalPlaces = &decimalPlaces
}
style.CustomNumFmt = &numFmt.FormatCode
if strings.Contains(numFmt.FormatCode, ";[Red]") {
style.NegRed = true
}
for numFmtID, fmtCode := range currencyNumFmt {
if style.NegRed {
fmtCode += ";[Red]" + fmtCode
}
if numFmt.FormatCode == fmtCode {
style.NumFmt = numFmtID
}
}
}
}
}
}
// extractAlignment provides a function to extract alignment format by
// given style definition.
func (f *File) extractAlignment(a *xlsxAlignment, s *xlsxStyleSheet, style *Style) {
if a != nil {
style.Alignment = &Alignment{
Horizontal: a.Horizontal,
Indent: a.Indent,
JustifyLastLine: a.JustifyLastLine,
ReadingOrder: a.ReadingOrder,
RelativeIndent: a.RelativeIndent,
ShrinkToFit: a.ShrinkToFit,
TextRotation: a.TextRotation,
Vertical: a.Vertical,
WrapText: a.WrapText,
}
}
}
// extractProtection provides a function to extract protection settings by
// given format definition.
func (f *File) extractProtection(p *xlsxProtection, s *xlsxStyleSheet, style *Style) {
if p != nil {
style.Protection = &Protection{}
if p.Hidden != nil {
style.Protection.Hidden = *p.Hidden
}
if p.Locked != nil {
style.Protection.Locked = *p.Locked
}
}
}
// GetStyle provides a function to get style definition by given style index.
func (f *File) GetStyle(idx int) (*Style, error) {
var style *Style
f.mu.Lock()
s, err := f.stylesReader()
if err != nil {
return style, err
}
f.mu.Unlock()
if idx < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= idx {
return style, newInvalidStyleID(idx)
}
style = &Style{}
xf := s.CellXfs.Xf[idx]
if extractStyleCondFuncs["fill"](xf, s) {
f.extractFills(s.Fills.Fill[*xf.FillID], s, style)
}
if extractStyleCondFuncs["border"](xf, s) {
f.extractBorders(s.Borders.Border[*xf.BorderID], s, style)
}
if extractStyleCondFuncs["font"](xf, s) {
style.Font = extractFont(s.Fonts.Font[*xf.FontID])
}
if extractStyleCondFuncs["alignment"](xf, s) {
f.extractAlignment(xf.Alignment, s, style)
}
if extractStyleCondFuncs["protection"](xf, s) {
f.extractProtection(xf.Protection, s, style)
}
f.extractNumFmt(xf.NumFmtID, s, style)
return style, nil
}
// getStyleID provides a function to get styleID by given style. If given
// style does not exist, will return -1.
func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) {
var (
err error
styleID = -1
)
if ss.CellXfs == nil {
return styleID, err
}
numFmtID, borderID, fillID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style)
fontID := f.getFontID(ss, style)
if style.CustomNumFmt != nil {
numFmtID = getCustomNumFmtID(ss, style)
}
for xfID, xf := range ss.CellXfs.Xf {
if getXfIDFuncs["numFmt"](numFmtID, xf, style) &&
getXfIDFuncs["font"](fontID, xf, style) &&
getXfIDFuncs["fill"](fillID, xf, style) &&
getXfIDFuncs["border"](borderID, xf, style) &&
getXfIDFuncs["alignment"](0, xf, style) &&
getXfIDFuncs["protection"](0, xf, style) {
styleID = xfID
return styleID, err
}
}
return styleID, err
}
// NewConditionalStyle provides a function to create style for conditional
// format by given style format. The parameters are the same with the NewStyle
// function.
func (f *File) NewConditionalStyle(style *Style) (int, error) {
f.mu.Lock()
s, err := f.stylesReader()
if err != nil {
f.mu.Unlock()
return 0, err
}
f.mu.Unlock()
fs, err := parseFormatStyleSet(style)
if err != nil {
return 0, err
}
if fs.DecimalPlaces != nil && (*fs.DecimalPlaces < 0 || *fs.DecimalPlaces > 30) {
fs.DecimalPlaces = intPtr(2)
}
dxf := xlsxDxf{
Fill: newFills(fs, false),
}
if fs.Alignment != nil {
dxf.Alignment = newAlignment(fs)
}
if len(fs.Border) > 0 {
dxf.Border = newBorders(fs)
}
if fs.Font != nil {
dxf.Font = fs.Font.newFont()
}
if fs.Protection != nil {
dxf.Protection = newProtection(fs)
}
dxf.NumFmt = newDxfNumFmt(s, style, &dxf)
if s.Dxfs == nil {
s.Dxfs = &xlsxDxfs{}
}
s.Dxfs.Count++
s.Dxfs.Dxfs = append(s.Dxfs.Dxfs, &dxf)
return s.Dxfs.Count - 1, nil
}
// GetConditionalStyle returns conditional format style definition by specified
// style index.
func (f *File) GetConditionalStyle(idx int) (*Style, error) {
var style *Style
f.mu.Lock()
s, err := f.stylesReader()
if err != nil {
return style, err
}
f.mu.Unlock()
if idx < 0 || s.Dxfs == nil || len(s.Dxfs.Dxfs) <= idx {
return style, newInvalidStyleID(idx)
}
style = &Style{}
xf := s.Dxfs.Dxfs[idx]
// The default pattern fill type of conditional format style is solid
if xf.Fill != nil && xf.Fill.PatternFill != nil && xf.Fill.PatternFill.PatternType == "" {
xf.Fill.PatternFill.PatternType = "solid"
}
f.extractFills(xf.Fill, s, style)
f.extractBorders(xf.Border, s, style)
style.Font = extractFont(xf.Font)
f.extractAlignment(xf.Alignment, s, style)
f.extractProtection(xf.Protection, s, style)
if xf.NumFmt != nil {
f.extractNumFmt(&xf.NumFmt.NumFmtID, s, style)
}
return style, nil
}
// newDxfNumFmt provides a function to create number format for conditional
// format styles.
func newDxfNumFmt(styleSheet *xlsxStyleSheet, style *Style, dxf *xlsxDxf) *xlsxNumFmt {
dp, numFmtID := "0", 164 // Default custom number format code from 164.
if style.DecimalPlaces != nil && *style.DecimalPlaces > 0 {
dp += "."
for i := 0; i < *style.DecimalPlaces; i++ {
dp += "0"
}
}
if style.CustomNumFmt != nil {
if styleSheet.Dxfs != nil {
for _, d := range styleSheet.Dxfs.Dxfs {
if d != nil && d.NumFmt != nil && d.NumFmt.NumFmtID > numFmtID {
numFmtID = d.NumFmt.NumFmtID
}
}
}
return &xlsxNumFmt{NumFmtID: numFmtID + 1, FormatCode: *style.CustomNumFmt}
}
numFmtCode, ok := builtInNumFmt[style.NumFmt]
if style.NumFmt > 0 && ok {
return &xlsxNumFmt{NumFmtID: style.NumFmt, FormatCode: numFmtCode}
}
fc, currency := currencyNumFmt[style.NumFmt]
if !currency {
return nil
}
if style.DecimalPlaces != nil {
fc = strings.ReplaceAll(fc, "0.00", dp)
}
if style.NegRed {
fc = fc + ";[Red]" + fc
}
return &xlsxNumFmt{NumFmtID: numFmtID, FormatCode: fc}
}
// GetDefaultFont provides the default font name currently set in the
// workbook. The spreadsheet generated by excelize default font is Calibri.
func (f *File) GetDefaultFont() (string, error) {
font, err := f.readDefaultFont()
if err != nil {
return "", err
}
return *font.Name.Val, err
}
// SetDefaultFont changes the default font in the workbook.
func (f *File) SetDefaultFont(fontName string) error {
font, err := f.readDefaultFont()
if err != nil {
return err
}
font.Name.Val = stringPtr(fontName)
f.mu.Lock()
s, _ := f.stylesReader()
f.mu.Unlock()
s.Fonts.Font[0] = font
custom := true
s.CellStyles.CellStyle[0].CustomBuiltIn = &custom
return err
}
// readDefaultFont provides an un-marshalled font value.
func (f *File) readDefaultFont() (*xlsxFont, error) {
f.mu.Lock()
defer f.mu.Unlock()
s, err := f.stylesReader()
if err != nil {
return nil, err
}
return s.Fonts.Font[0], err
}
// getFontID provides a function to get font ID.
// If given font does not exist, will return -1.
func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) int {
fontID := -1
if styleSheet.Fonts == nil || style.Font == nil {
return fontID
}
for idx, fnt := range styleSheet.Fonts.Font {
if reflect.DeepEqual(*fnt, *style.Font.newFont()) {
fontID = idx
return fontID
}
}
return fontID
}
// newFontColor set font color by given styles.
func newFontColor(font *Font) *xlsxColor {
var fontColor *xlsxColor
prepareFontColor := func() {
if fontColor != nil {
return
}
fontColor = &xlsxColor{}
}
if font.Color != "" {
prepareFontColor()
fontColor.RGB = getPaletteColor(font.Color)
}
if font.ColorIndexed >= 0 && font.ColorIndexed <= len(IndexedColorMapping)+1 {
prepareFontColor()
fontColor.Indexed = font.ColorIndexed
}
if font.ColorTheme != nil {
prepareFontColor()
fontColor.Theme = font.ColorTheme
}
if font.ColorTint != 0 {
prepareFontColor()
fontColor.Tint = font.ColorTint
}
return fontColor
}
// newFont provides a function to add font style by given cell format
// settings.
func (fnt *Font) newFont() *xlsxFont {
font := xlsxFont{Family: &attrValInt{Val: intPtr(2)}}
if fnt.Size >= MinFontSize {
font.Sz = &attrValFloat{Val: float64Ptr(fnt.Size)}
}
if fnt.Family != "" {
font.Name = &attrValString{Val: stringPtr(fnt.Family)}
}
if fnt.Charset != nil {
font.Charset = &attrValInt{Val: fnt.Charset}
}
font.Color = newFontColor(fnt)
if fnt.Bold {
font.B = &attrValBool{Val: &fnt.Bold}
}
if fnt.Italic {
font.I = &attrValBool{Val: &fnt.Italic}
}
if fnt.Strike {
font.Strike = &attrValBool{Val: &fnt.Strike}
}
if idx := inStrSlice(supportedUnderlineTypes, fnt.Underline, true); idx != -1 {
font.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])}
}
if inStrSlice(supportedVertAlignTypes, fnt.VertAlign, true) != -1 {
font.VertAlign = &attrValString{Val: &fnt.VertAlign}
}
return &font
}
// getNumFmtID provides a function to get number format code ID.
// If given number format code does not exist, will return -1.
func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) int {
numFmtID := -1
if _, ok := builtInNumFmt[style.NumFmt]; ok {
return style.NumFmt
}
if (27 <= style.NumFmt && style.NumFmt <= 36) || (50 <= style.NumFmt && style.NumFmt <= 81) {
return style.NumFmt
}
if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok {
numFmtID = style.NumFmt
if styleSheet.NumFmts != nil {
for _, numFmt := range styleSheet.NumFmts.NumFmt {
if numFmt.FormatCode == fmtCode {
return numFmt.NumFmtID
}
}
}
}
return numFmtID
}
// newNumFmt provides a function to check if number format code in the range
// of built-in values.
func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
dp, numFmtID := "0", 164 // Default custom number format code from 164.
if style.DecimalPlaces != nil && *style.DecimalPlaces > 0 {
dp += "."
for i := 0; i < *style.DecimalPlaces; i++ {
dp += "0"
}
}
if style.CustomNumFmt != nil {
if customNumFmtID := getCustomNumFmtID(styleSheet, style); customNumFmtID != -1 {
return customNumFmtID
}
return setCustomNumFmt(styleSheet, style)
}
if _, ok := builtInNumFmt[style.NumFmt]; !ok {
fc, currency := currencyNumFmt[style.NumFmt]
if !currency {
return setLangNumFmt(style)
}
if style.DecimalPlaces != nil {
fc = strings.ReplaceAll(fc, "0.00", dp)
}
if style.NegRed {
fc = fc + ";[Red]" + fc
}
if styleSheet.NumFmts == nil {
styleSheet.NumFmts = &xlsxNumFmts{NumFmt: []*xlsxNumFmt{}}
} else {
numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1
}
styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &xlsxNumFmt{
FormatCode: fc, NumFmtID: numFmtID,
})
styleSheet.NumFmts.Count++
return numFmtID
}
return style.NumFmt
}
// setCustomNumFmt provides a function to set custom number format code.
func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
nf := xlsxNumFmt{NumFmtID: 163, FormatCode: *style.CustomNumFmt}
if styleSheet.NumFmts == nil {
styleSheet.NumFmts = &xlsxNumFmts{}
}
for _, numFmt := range styleSheet.NumFmts.NumFmt {
if numFmt != nil && nf.NumFmtID < numFmt.NumFmtID {
nf.NumFmtID = numFmt.NumFmtID
}
}
nf.NumFmtID++
styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf)
styleSheet.NumFmts.Count = len(styleSheet.NumFmts.NumFmt)
return nf.NumFmtID
}
// getCustomNumFmtID provides a function to get custom number format code ID.
// If given custom number format code does not exist, will return -1.
func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID int) {
customNumFmtID = -1
if styleSheet.NumFmts == nil {
return
}
for _, numFmt := range styleSheet.NumFmts.NumFmt {
if style.CustomNumFmt != nil && numFmt.FormatCode == *style.CustomNumFmt {
customNumFmtID = numFmt.NumFmtID
return
}
}
return
}
// isLangNumFmt provides a function to returns if a given number format ID is a
// built-in language glyphs number format code.
func isLangNumFmt(ID int) bool {
return (27 <= ID && ID <= 36) || (50 <= ID && ID <= 62) || (67 <= ID && ID <= 81)
}
// setLangNumFmt provides a function to set number format code with language.
func setLangNumFmt(style *Style) int {
if isLangNumFmt(style.NumFmt) {
return style.NumFmt
}
return 0
}
// getFillID provides a function to get fill ID. If given fill is not
// exist, will return -1.
func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) {
fillID = -1
if styleSheet.Fills == nil || style.Fill.Type == "" {
return
}
fills := newFills(style, true)
if fills == nil {
return
}
for idx, fill := range styleSheet.Fills.Fill {
if reflect.DeepEqual(fill, fills) {
fillID = idx
return
}
}
return
}
// newFills provides a function to add fill elements in the styles.xml by
// given cell format settings.
func newFills(style *Style, fg bool) *xlsxFill {
var fill xlsxFill
switch style.Fill.Type {
case "gradient":
gradient := styleFillVariants()[style.Fill.Shading]
gradient.Stop[0].Color.RGB = getPaletteColor(style.Fill.Color[0])
gradient.Stop[1].Color.RGB = getPaletteColor(style.Fill.Color[1])
if len(gradient.Stop) == 3 {
gradient.Stop[2].Color.RGB = getPaletteColor(style.Fill.Color[0])
}
fill.GradientFill = &gradient
case "pattern":
var pattern xlsxPatternFill
pattern.PatternType = styleFillPatterns[style.Fill.Pattern]
if len(style.Fill.Color) < 1 {
fill.PatternFill = &pattern
break
}
if fg {
if pattern.FgColor == nil {
pattern.FgColor = new(xlsxColor)
}
pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0])
} else {
if pattern.BgColor == nil {
pattern.BgColor = new(xlsxColor)
}
pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0])
}
fill.PatternFill = &pattern
default:
return nil
}
return &fill
}
// newAlignment provides a function to formatting information pertaining to
// text alignment in cells. There are a variety of choices for how text is
// aligned both horizontally and vertically, as well as indentation settings,
// and so on.
func newAlignment(style *Style) *xlsxAlignment {
var alignment xlsxAlignment
if style.Alignment != nil {
alignment.Horizontal = style.Alignment.Horizontal
alignment.Indent = style.Alignment.Indent
alignment.JustifyLastLine = style.Alignment.JustifyLastLine
alignment.ReadingOrder = style.Alignment.ReadingOrder
alignment.RelativeIndent = style.Alignment.RelativeIndent
alignment.ShrinkToFit = style.Alignment.ShrinkToFit
alignment.TextRotation = style.Alignment.TextRotation
alignment.Vertical = style.Alignment.Vertical
alignment.WrapText = style.Alignment.WrapText
}
return &alignment
}
// newProtection provides a function to set protection properties associated
// with the cell.
func newProtection(style *Style) *xlsxProtection {
var protection xlsxProtection
if style.Protection != nil {
protection.Hidden = &style.Protection.Hidden
protection.Locked = &style.Protection.Locked
}
return &protection
}
// getBorderID provides a function to get border ID. If given border is not
// exist, will return -1.
func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) {
borderID = -1
if styleSheet.Borders == nil || len(style.Border) == 0 {
return
}
for idx, border := range styleSheet.Borders.Border {
if reflect.DeepEqual(*border, *newBorders(style)) {
borderID = idx
return
}
}
return
}
// newBorders provides a function to add border elements in the styles.xml by
// given borders format settings.
func newBorders(style *Style) *xlsxBorder {
var border xlsxBorder
for _, v := range style.Border {
if 0 <= v.Style && v.Style < 14 {
line := &xlsxLine{Style: styleBorders[v.Style], Color: &xlsxColor{RGB: getPaletteColor(v.Color)}}
switch v.Type {
case "left":
border.Left = line
case "right":
border.Right = line
case "top":
border.Top = line
case "bottom":
border.Bottom = line
case "diagonalUp":
border.Diagonal, border.DiagonalUp = line, true
case "diagonalDown":
border.Diagonal, border.DiagonalDown = line, true
}
}
}
return &border
}
// setCellXfs provides a function to set describes all the formatting for a
// cell.
func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) {
var xf xlsxXf
xf.FontID = intPtr(fontID)
if fontID != 0 {
xf.ApplyFont = boolPtr(true)
}
xf.NumFmtID = intPtr(numFmtID)
if numFmtID != 0 {
xf.ApplyNumberFormat = boolPtr(true)
}
xf.FillID = intPtr(fillID)
if fillID != 0 {
xf.ApplyFill = boolPtr(true)
}
xf.BorderID = intPtr(borderID)
if borderID != 0 {
xf.ApplyBorder = boolPtr(true)
}
if len(style.CellXfs.Xf) == MaxCellStyles {
return 0, ErrCellStyles
}
style.CellXfs.Count = len(style.CellXfs.Xf) + 1
xf.Alignment = alignment
if alignment != nil {
xf.ApplyAlignment = boolPtr(applyAlignment)
}
if applyProtection {
xf.ApplyProtection = boolPtr(applyProtection)
xf.Protection = protection
}
xfID := 0
xf.XfID = &xfID
style.CellXfs.Xf = append(style.CellXfs.Xf, xf)
return style.CellXfs.Count - 1, nil
}
// GetCellStyle provides a function to get cell style index by given worksheet
// name and cell reference. This function is concurrency safe.
func (f *File) GetCellStyle(sheet, cell string) (int, error) {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return 0, err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return 0, err
}
ws.prepareSheetXML(col, row)
return ws.prepareCellStyle(col, row, ws.SheetData.Row[row-1].C[col-1].S), err
}
// SetCellStyle provides a function to add style attribute for cells by given
// worksheet name, range reference and style ID. This function is concurrency
// safe. Note that diagonalDown and diagonalUp type border should be use same
// color in the same range. SetCellStyle will overwrite the existing
// styles for the cell, it won't append or merge style with existing styles.
//
// For example create a borders of cell H9 on Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Border: []excelize.Border{
// {Type: "left", Color: "0000FF", Style: 3},
// {Type: "top", Color: "00FF00", Style: 4},
// {Type: "bottom", Color: "FFFF00", Style: 5},
// {Type: "right", Color: "FF0000", Style: 6},
// {Type: "diagonalDown", Color: "A020F0", Style: 7},
// {Type: "diagonalUp", Color: "A020F0", Style: 8},
// },
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set gradient fill with vertical variants shading styles for cell H9 on
// Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Fill: excelize.Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 1},
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set solid style pattern fill for cell H9 on Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Fill: excelize.Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 1},
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set alignment style for cell H9 on Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Alignment: &excelize.Alignment{
// Horizontal: "center",
// Indent: 1,
// JustifyLastLine: true,
// ReadingOrder: 0,
// RelativeIndent: 1,
// ShrinkToFit: true,
// TextRotation: 45,
// Vertical: "",
// WrapText: true,
// },
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Dates and times in Excel are represented by real numbers, for example "Apr 7
// 2017 12:00 PM" is represented by the number 42920.5. Set date and time format
// for cell H9 on Sheet1:
//
// f.SetCellValue("Sheet1", "H9", 42920.5)
// style, err := f.NewStyle(&excelize.Style{NumFmt: 22})
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set font style for cell H9 on Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Font: &excelize.Font{
// Bold: true,
// Italic: true,
// Family: "Times New Roman",
// Size: 36,
// Color: "777777",
// },
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Hide and lock for cell H9 on Sheet1:
//
// style, err := f.NewStyle(&excelize.Style{
// Protection: &excelize.Protection{
// Hidden: true,
// Locked: true,
// },
// })
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID int) error {
hCol, hRow, err := CellNameToCoordinates(topLeftCell)
if err != nil {
return err
}
vCol, vRow, err := CellNameToCoordinates(bottomRightCell)
if err != nil {
return err
}
// Normalize the range, such correct C1:B3 to B1:C3.
if vCol < hCol {
vCol, hCol = hCol, vCol
}
if vRow < hRow {
vRow, hRow = hRow, vRow
}
hColIdx := hCol - 1
hRowIdx := hRow - 1
vColIdx := vCol - 1
vRowIdx := vRow - 1
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
s, err := f.stylesReader()
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
ws.prepareSheetXML(vCol, vRow)
ws.makeContiguousColumns(hRow, vRow, vCol)
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
return newInvalidStyleID(styleID)
}
for r := hRowIdx; r <= vRowIdx; r++ {
for k := hColIdx; k <= vColIdx; k++ {
ws.SheetData.Row[r].C[k].S = styleID
}
}
return err
}
// SetConditionalFormat provides a function to create conditional formatting
// rule for cell value. Conditional formatting is a feature of Excel which
// allows you to apply a format to a cell or a range of cells based on certain
// criteria.
//
// The type option is a required parameter and it has no default value.
// Allowable type values and their associated parameters are:
//
// Type | Parameters
// ---------------+------------------------------------
// cell | Criteria
// | Value
// | MinValue
// | MaxValue
// time_period | Criteria
// text | Criteria
// | Value
// average | Criteria
// duplicate | (none)
// unique | (none)
// top | Criteria
// | Value
// bottom | Criteria
// | Value
// blanks | (none)
// no_blanks | (none)
// errors | (none)
// no_errors | (none)
// 2_color_scale | MinType
// | MaxType
// | MinValue
// | MaxValue
// | MinColor
// | MaxColor
// 3_color_scale | MinType
// | MidType
// | MaxType
// | MinValue
// | MidValue
// | MaxValue
// | MinColor
// | MidColor
// | MaxColor
// data_bar | MinType
// | MaxType
// | MinValue
// | MaxValue
// | BarBorderColor
// | BarColor
// | BarDirection
// | BarOnly
// | BarSolid
// icon_set | IconStyle
// | ReverseIcons
// | IconsOnly
// formula | Criteria
//
// The 'Criteria' parameter is used to set the criteria by which the cell data
// will be evaluated. It has no default value. The most common criteria as
// applied to {Type: "cell"} are:
//
// between |
// not between |
// equal to | ==
// not equal to | !=
// greater than | >
// less than | <
// greater than or equal to | >=
// less than or equal to | <=
//
// You can either use Excel's textual description strings, in the first column
// above, or the more common symbolic alternatives.
//
// Additional criteria which are specific to other conditional format types are
// shown in the relevant sections below.
//
// value: The value is generally used along with the criteria parameter to set
// the rule by which the cell data will be evaluated:
//
// err := f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: ">",
// Format: &format,
// Value: "6",
// },
// },
// )
//
// The value property can also be an cell reference:
//
// err := f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: ">",
// Format: &format,
// Value: "$C$1",
// },
// },
// )
//
// type: format - The format parameter is used to specify the format that will
// be applied to the cell when the conditional formatting criterion is met. The
// format is created using the NewConditionalStyle function in the same way as
// cell formats:
//
// format, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "9A0511"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1,
// },
// },
// )
// if err != nil {
// fmt.Println(err)
// }
// err = f.SetConditionalFormat("Sheet1", "D1:D10",
// []excelize.ConditionalFormatOptions{
// {Type: "cell", Criteria: ">", Format: &format, Value: "6"},
// },
// )
//
// Note: In Excel, a conditional format is superimposed over the existing cell
// format and not all cell format properties can be modified. Properties that
// cannot be modified in a conditional format are font name, font size,
// superscript and subscript, diagonal borders, all alignment properties and all
// protection properties.
//
// Excel specifies some default formats to be used with conditional formatting.
// These can be replicated using the following excelize formats:
//
// // Rose format for bad conditional.
// format1, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "9A0511"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1,
// },
// },
// )
//
// // Light yellow format for neutral conditional.
// format2, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "9B5713"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"FEEAA0"}, Pattern: 1,
// },
// },
// )
//
// // Light green format for good conditional.
// format3, err := f.NewConditionalStyle(
// &excelize.Style{
// Font: &excelize.Font{Color: "09600B"},
// Fill: excelize.Fill{
// Type: "pattern", Color: []string{"C7EECF"}, Pattern: 1,
// },
// },
// )
//
// type: MinValue - The 'MinValue' parameter is used to set the lower limiting
// value when the criteria is either "between" or "not between".
//
// // Highlight cells rules: between...
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "cell",
// Criteria: "between",
// Format: &format,
// MinValue: 6",
// MaxValue: 8",
// },
// },
// )
//
// type: MaxValue - The 'MaxValue' parameter is used to set the upper limiting
// value when the criteria is either "between" or "not between". See the
// previous example.
//
// type: average - The average type is used to specify Excel's "Average" style
// conditional format:
//
// // Top/Bottom rules: Above Average...
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "average",
// Criteria: "=",
// Format: &format1,
// AboveAverage: true,
// },
// },
// )
//
// // Top/Bottom rules: Below Average...
// err := f.SetConditionalFormat("Sheet1", "B1:B10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "average",
// Criteria: "=",
// Format: &format2,
// AboveAverage: false,
// },
// },
// )
//
// type: duplicate - The duplicate type is used to highlight duplicate cells in
// a range:
//
// // Highlight cells rules: Duplicate Values...
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {Type: "duplicate", Criteria: "=", Format: &format},
// },
// )
//
// type: unique - The unique type is used to highlight unique cells in a range:
//
// // Highlight cells rules: Not Equal To...
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {Type: "unique", Criteria: "=", Format: &format},
// },
// )
//
// type: top - The top type is used to specify the top n values by number or
// percentage in a range:
//
// // Top/Bottom rules: Top 10.
// err := f.SetConditionalFormat("Sheet1", "H1:H10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "top",
// Criteria: "=",
// Format: &format,
// Value: "6",
// },
// },
// )
//
// The criteria can be used to indicate that a percentage condition is required:
//
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "top",
// Criteria: "=",
// Format: &format,
// Value: "6",
// Percent: true,
// },
// },
// )
//
// type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2
// Color Scale" style conditional format:
//
// // Color scales: 2 color.
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "2_color_scale",
// Criteria: "=",
// MinType: "min",
// MaxType: "max",
// MinColor: "#F8696B",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// This conditional type can be modified with MinType, MaxType, MinValue,
// MaxValue, MinColor and MaxColor, see below.
//
// type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3
// Color Scale" style conditional format:
//
// // Color scales: 3 color.
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "3_color_scale",
// Criteria: "=",
// MinType: "min",
// MidType: "percentile",
// MaxType: "max",
// MinColor: "#F8696B",
// MidColor: "#FFEB84",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// This conditional type can be modified with MinType, MidType, MaxType,
// MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see
// below.
//
// type: data_bar - The data_bar type is used to specify Excel's "Data Bar"
// style conditional format.
//
// MinType - The MinType and MaxType properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
// The MidType is available for 3_color_scale. The properties are used as
// follows:
//
// // Data Bars: Gradient Fill.
// err := f.SetConditionalFormat("Sheet1", "K1:K10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "data_bar",
// Criteria: "=",
// MinType: "min",
// MaxType: "max",
// BarColor: "#638EC6",
// },
// },
// )
//
// The available min/mid/max types are:
//
// min (for MinType only)
// num
// percent
// percentile
// formula
// max (for MaxType only)
//
// MidType - Used for 3_color_scale. Same as MinType, see above.
//
// MaxType - Same as MinType, see above.
//
// MinValue - The MinValue and MaxValue properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
//
// MidValue - The MidValue is available for 3_color_scale. Same as MinValue,
// see above.
//
// MaxValue - Same as MinValue, see above.
//
// MinColor - The MinColor and MaxColor properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
//
// MidColor - The MidColor is available for 3_color_scale. The properties
// are used as follows:
//
// // Color scales: 3 color.
// err := f.SetConditionalFormat("Sheet1", "B1:B10",
// []excelize.ConditionalFormatOptions{
// {
// Type: "3_color_scale",
// Criteria: "=",
// MinType: "min",
// MidType: "percentile",
// MaxType: "max",
// MinColor: "#F8696B",
// MidColor: "#FFEB84",
// MaxColor: "#63BE7B",
// },
// },
// )
//
// MaxColor - Same as MinColor, see above.
//
// BarColor - Used for data_bar. Same as MinColor, see above.
//
// BarBorderColor - Used for sets the color for the border line of a data bar,
// this is only visible in Excel 2010 and later.
//
// BarDirection - sets the direction for data bars. The available options are:
//
// context - Data bar direction is set by spreadsheet application based on the context of the data displayed.
// leftToRight - Data bar direction is from right to left.
// rightToLeft - Data bar direction is from left to right.
//
// BarOnly - Used for set displays a bar data but not the data in the cells.
//
// BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this
// is only visible in Excel 2010 and later.
//
// IconStyle - The available options are:
//
// 3Arrows
// 3ArrowsGray
// 3Flags
// 3Signs
// 3Stars
// 3Symbols
// 3Symbols2
// 3TrafficLights1
// 3TrafficLights2
// 3Triangles
// 4Arrows
// 4ArrowsGray
// 4Rating
// 4RedToBlack
// 4TrafficLights
// 5Arrows
// 5ArrowsGray
// 5Boxes
// 5Quarters
// 5Rating
//
// ReverseIcons - Used for set reversed icons sets.
//
// IconsOnly - Used for set displayed without the cell value.
//
// StopIfTrue - used to set the "stop if true" feature of a conditional
// formatting rule when more than one rule is applied to a cell or a range of
// cells. When this parameter is set then subsequent rules are not evaluated
// if the current rule is true.
func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFormatOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
SQRef, mastCell, err := prepareConditionalFormatRange(rangeRef)
if err != nil {
return err
}
// Create a pseudo GUID for each unique rule.
var rules int
for _, cf := range ws.ConditionalFormatting {
rules += len(cf.CfRule)
}
var (
cfRule []*xlsxCfRule
noCriteriaTypes = []string{
"containsBlanks",
"notContainsBlanks",
"containsErrors",
"notContainsErrors",
"expression",
"iconSet",
}
)
for i, opt := range opts {
var vt, ct string
var ok bool
// "type" is a required parameter, check for valid validation types.
vt, ok = validType[opt.Type]
if ok {
// Check for valid criteria types.
ct, ok = criteriaType[opt.Criteria]
if ok || inStrSlice(noCriteriaTypes, vt, true) != -1 {
drawFunc, ok := drawContFmtFunc[vt]
if ok {
priority := rules + i
rule, x14rule := drawFunc(priority, ct, mastCell,
fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), priority), &opt)
if rule == nil && x14rule == nil {
return ErrParameterInvalid
}
if rule != nil {
cfRule = append(cfRule, rule)
}
if x14rule != nil {
if err = f.appendCfRule(ws, x14rule, SQRef); err != nil {
return err
}
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
}
continue
}
}
return ErrParameterInvalid
}
return ErrParameterInvalid
}
if len(cfRule) > 0 {
ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
SQRef: SQRef,
CfRule: cfRule,
})
}
return err
}
// prepareConditionalFormatRange returns checked cell range and master cell
// reference by giving conditional formatting range reference.
func prepareConditionalFormatRange(rangeRef string) (string, string, error) {
var SQRef, mastCell string
if rangeRef == "" {
return SQRef, mastCell, ErrParameterRequired
}
rangeRef = strings.ReplaceAll(rangeRef, ",", " ")
for i, cellRange := range strings.Split(rangeRef, " ") {
var cellNames []string
for j, ref := range strings.Split(cellRange, ":") {
if j > 1 {
return SQRef, mastCell, ErrParameterInvalid
}
cellRef, col, row, err := parseRef(ref)
if err != nil {
return SQRef, mastCell, err
}
var c, r int
if col {
if cellRef.Row = TotalRows; j == 0 {
cellRef.Row = 1
}
}
if row {
if cellRef.Col = MaxColumns; j == 0 {
cellRef.Col = 1
}
}
c, r = cellRef.Col, cellRef.Row
cellName, _ := CoordinatesToCellName(c, r)
cellNames = append(cellNames, cellName)
if i == 0 && j == 0 {
mastCell = cellName
}
}
SQRef += strings.Join(cellNames, ":") + " "
}
return strings.TrimSuffix(SQRef, " "), mastCell, nil
}
// appendCfRule provides a function to append rules to conditional formatting.
func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule, sqref string) error {
var (
err error
idx int
appendMode bool
decodeExtLst = new(decodeExtLst)
condFmts *xlsxX14ConditionalFormattings
decodeCondFmts *decodeX14ConditionalFormattings
ext *xlsxExt
condFmtBytes, condFmtsBytes, extLstBytes []byte
)
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{
{XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, Sqref: sqref, CfRule: []*xlsxX14CfRule{rule}},
})
if ws.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts = new(decodeX14ConditionalFormattings)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
if condFmts == nil {
condFmts = &xlsxX14ConditionalFormattings{}
}
condFmts.Content = decodeCondFmts.Content + string(condFmtBytes)
condFmtsBytes, _ = xml.Marshal(condFmts)
decodeExtLst.Ext[idx].Content = string(condFmtsBytes)
appendMode = true
}
}
}
if !appendMode {
condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)})
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes),
})
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
return err
}
// extractCondFmtCellIs provides a function to extract conditional format
// settings for cell value (include between, not between, equal, not equal,
// greater than and less than) by given conditional formatting rule.
func (f *File) extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "cell", Criteria: operatorType[c.Operator]}
if len(c.Formula) == 2 {
format.MinValue, format.MaxValue = c.Formula[0], c.Formula[1]
return format
}
format.Value = c.Formula[0]
return format
}
// extractCondFmtTimePeriod provides a function to extract conditional format
// settings for time period by given conditional formatting rule.
func (f *File) extractCondFmtTimePeriod(c *xlsxCfRule, ref string, extLst *xlsxExtLst) ConditionalFormatOptions {
var criteria string
for _, formula := range c.Formula {
if c, ok := map[string]string{
fmt.Sprintf("FLOOR(%s,1)=TODAY()-1", ref): "yesterday",
fmt.Sprintf("FLOOR(%s,1)=TODAY()", ref): "today",
fmt.Sprintf("FLOOR(%s,1)=TODAY()+1", ref): "tomorrow",
fmt.Sprintf("AND(TODAY()-FLOOR(%[1]s,1)<=6,FLOOR(%[1]s,1)<=TODAY())", ref): "last 7 days",
fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%[1]s,0)<(WEEKDAY(TODAY())+7))", ref): "last week",
fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%[1]s,0)-TODAY()>=7-WEEKDAY(TODAY()))", ref): "this week",
fmt.Sprintf("AND(ROUNDDOWN(%[1]s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%[1]s,0)-TODAY()<(15-WEEKDAY(TODAY())))", ref): "continue week",
fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())-1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=1,YEAR(%[1]s)=YEAR(TODAY())-1)))", ref): "last month",
fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY()),YEAR(%[1]s)=YEAR(TODAY()))", ref): "this month",
fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())+1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=12,YEAR(%[1]s)=YEAR(TODAY())+1)))", ref): "continue month",
}[formula]; ok {
criteria = c
break
}
}
return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "time_period", Criteria: criteria}
}
// extractCondFmtText provides a function to extract conditional format
// settings for text cell values by given conditional formatting rule.
func (f *File) extractCondFmtText(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "text", Criteria: operatorType[c.Operator], Value: c.Text}
}
// extractCondFmtTop10 provides a function to extract conditional format
// settings for top N (default is top 10) by given conditional formatting
// rule.
func (f *File) extractCondFmtTop10(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "top",
Criteria: "=",
Percent: c.Percent,
Value: strconv.Itoa(c.Rank),
}
if c.Bottom {
format.Type = "bottom"
}
return format
}
// extractCondFmtAboveAverage provides a function to extract conditional format
// settings for above average and below average by given conditional formatting
// rule.
func (f *File) extractCondFmtAboveAverage(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "average",
Criteria: "=",
}
if c.AboveAverage != nil {
format.AboveAverage = *c.AboveAverage
}
return format
}
// extractCondFmtDuplicateUniqueValues provides a function to extract
// conditional format settings for duplicate and unique values by given
// conditional formatting rule.
func (f *File) extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: map[string]string{
"duplicateValues": "duplicate",
"uniqueValues": "unique",
}[c.Type],
Criteria: "=",
}
}
// extractCondFmtBlanks provides a function to extract conditional format
// settings for blank cells by given conditional formatting rule.
func (f *File) extractCondFmtBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "blanks",
}
}
// extractCondFmtNoBlanks provides a function to extract conditional format
// settings for no blank cells by given conditional formatting rule.
func (f *File) extractCondFmtNoBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "no_blanks",
}
}
// extractCondFmtErrors provides a function to extract conditional format
// settings for cells with errors by given conditional formatting rule.
func (f *File) extractCondFmtErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "errors",
}
}
// extractCondFmtNoErrors provides a function to extract conditional format
// settings for cells without errors by given conditional formatting rule.
func (f *File) extractCondFmtNoErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
Format: c.DxfID,
StopIfTrue: c.StopIfTrue,
Type: "no_errors",
}
}
// extractCondFmtColorScale provides a function to extract conditional format
// settings for color scale (include 2 color scale and 3 color scale) by given
// conditional formatting rule.
func (f *File) extractCondFmtColorScale(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue}
format.Type, format.Criteria = "2_color_scale", "="
values := len(c.ColorScale.Cfvo)
colors := len(c.ColorScale.Color)
if colors > 1 && values > 1 {
format.MinType = c.ColorScale.Cfvo[0].Type
if c.ColorScale.Cfvo[0].Val != "0" {
format.MinValue = c.ColorScale.Cfvo[0].Val
}
format.MinColor = "#" + f.getThemeColor(c.ColorScale.Color[0])
format.MaxType = c.ColorScale.Cfvo[1].Type
if c.ColorScale.Cfvo[1].Val != "0" {
format.MaxValue = c.ColorScale.Cfvo[1].Val
}
format.MaxColor = "#" + f.getThemeColor(c.ColorScale.Color[1])
}
if colors == 3 {
format.Type = "3_color_scale"
format.MidType = c.ColorScale.Cfvo[1].Type
if c.ColorScale.Cfvo[1].Val != "0" {
format.MidValue = c.ColorScale.Cfvo[1].Val
}
format.MidColor = "#" + f.getThemeColor(c.ColorScale.Color[1])
format.MaxType = c.ColorScale.Cfvo[2].Type
if c.ColorScale.Cfvo[2].Val != "0" {
format.MaxValue = c.ColorScale.Cfvo[2].Val
}
format.MaxColor = "#" + f.getThemeColor(c.ColorScale.Color[2])
}
return format
}
// extractCondFmtDataBarRule provides a function to extract conditional format
// settings for data bar by given conditional formatting rule extension list.
func (f *File) extractCondFmtDataBarRule(ID string, format *ConditionalFormatOptions, condFmts []decodeX14ConditionalFormatting) {
for _, condFmt := range condFmts {
for _, rule := range condFmt.CfRule {
if rule.DataBar != nil && rule.ID == ID {
format.BarDirection = rule.DataBar.Direction
if rule.DataBar.Gradient != nil && !*rule.DataBar.Gradient {
format.BarSolid = true
}
if rule.DataBar.BorderColor != nil {
format.BarBorderColor = "#" + f.getThemeColor(rule.DataBar.BorderColor)
}
}
}
}
}
// extractCondFmtDataBar provides a function to extract conditional format
// settings for data bar by given conditional formatting rule.
func (f *File) extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="}
if c.DataBar != nil {
format.StopIfTrue = c.StopIfTrue
format.MinType = c.DataBar.Cfvo[0].Type
format.MinValue = c.DataBar.Cfvo[0].Val
format.MaxType = c.DataBar.Cfvo[1].Type
format.MaxValue = c.DataBar.Cfvo[1].Val
format.BarColor = "#" + f.getThemeColor(c.DataBar.Color[0])
if c.DataBar.ShowValue != nil {
format.BarOnly = !*c.DataBar.ShowValue
}
}
extractExtLst := func(ID string, extLst *decodeExtLst) {
for _, ext := range extLst.Ext {
if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts := new(decodeX14ConditionalFormattingRules)
if err := xml.Unmarshal([]byte(ext.Content), &decodeCondFmts); err == nil {
f.extractCondFmtDataBarRule(ID, &format, decodeCondFmts.CondFmt)
}
}
}
}
if c.ExtLst != nil {
ext := decodeX14ConditionalFormattingExt{}
if err := xml.Unmarshal([]byte(c.ExtLst.Ext), &ext); err == nil && extLst != nil {
decodeExtLst := new(decodeExtLst)
if err = xml.Unmarshal([]byte(""+extLst.Ext+""), decodeExtLst); err == nil {
extractExtLst(ext.ID, decodeExtLst)
}
}
}
return format
}
// extractCondFmtExp provides a function to extract conditional format settings
// for expression by given conditional formatting rule.
func (f *File) extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "formula"}
if len(c.Formula) > 0 {
format.Criteria = c.Formula[0]
}
return format
}
// extractCondFmtIconSet provides a function to extract conditional format
// settings for icon sets by given conditional formatting rule.
func (f *File) extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "icon_set"}
if c.IconSet != nil {
if c.IconSet.ShowValue != nil {
format.IconsOnly = !*c.IconSet.ShowValue
}
format.IconStyle = c.IconSet.IconSet
format.ReverseIcons = c.IconSet.Reverse
}
return format
}
// extractX14CondFmtIconSet provides a function to extract conditional format
// settings for icon sets by given conditional formatting rule.
func (f *File) extractX14CondFmtIconSetRule(c *decodeX14CfRule) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "icon_set"}
if c.IconSet != nil {
if c.IconSet.ShowValue != nil {
format.IconsOnly = !*c.IconSet.ShowValue
}
format.IconStyle = c.IconSet.IconSet
format.ReverseIcons = c.IconSet.Reverse
}
return format
}
// GetConditionalFormats returns conditional format settings by given worksheet
// name.
func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) {
conditionalFormats := make(map[string][]ConditionalFormatOptions)
ws, err := f.workSheetReader(sheet)
if err != nil {
return conditionalFormats, err
}
for _, cf := range ws.ConditionalFormatting {
var opts []ConditionalFormatOptions
_, mastCell, err := prepareConditionalFormatRange(cf.SQRef)
if err != nil {
return conditionalFormats, err
}
for _, cr := range cf.CfRule {
if extractFunc, ok := extractContFmtFunc[cr.Type]; ok {
opts = append(opts, extractFunc(f, mastCell, cr, ws.ExtLst))
}
}
conditionalFormats[cf.SQRef] = opts
}
if ws.ExtLst != nil {
decodeExtLst := new(decodeExtLst)
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return conditionalFormats, err
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts := new(decodeX14ConditionalFormattingRules)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
for _, condFmt := range decodeCondFmts.CondFmt {
var opts []ConditionalFormatOptions
for _, rule := range condFmt.CfRule {
if rule.Type == "iconSet" {
opts = append(opts, f.extractX14CondFmtIconSetRule(rule))
}
}
conditionalFormats[condFmt.Sqref] = append(conditionalFormats[condFmt.Sqref], opts...)
}
}
}
}
return conditionalFormats, err
}
// UnsetConditionalFormat provides a function to unset the conditional format
// by given worksheet name and range reference.
func (f *File) UnsetConditionalFormat(sheet, rangeRef string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
delCells, err := flatSqref(rangeRef)
if err != nil {
return err
}
for i := 0; i < len(ws.ConditionalFormatting); i++ {
if ws.ConditionalFormatting[i].SQRef, err = deleteCellsFromSqref(ws.ConditionalFormatting[i].SQRef, delCells); err != nil {
return err
}
if len(ws.ConditionalFormatting[i].SQRef) == 0 {
ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...)
i--
}
}
if ws.ExtLst != nil {
var condFmtsBytes, extLstBytes []byte
decodeExtLst := new(decodeExtLst)
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for idx := 0; idx < len(decodeExtLst.Ext); idx++ {
ext := decodeExtLst.Ext[idx]
if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts := new(decodeX14ConditionalFormattingRules)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
if condFmtsBytes, err = decodeCondFmts.deleteCfRule(delCells); err != nil {
return err
}
decodeExtLst.Ext[idx].Content = string(condFmtsBytes)
if len(decodeExtLst.Ext[idx].Content) == 57 { // empty x14:conditionalFormattings element
decodeExtLst.Ext = append(decodeExtLst.Ext[:idx], decodeExtLst.Ext[idx+1:]...)
idx--
}
}
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")}
if len(ws.ExtLst.Ext) == 0 {
ws.ExtLst = nil
}
}
return err
}
// deleteCfRule provides a function to delete conditional formatting rule by
// given range reference and return the updated x14:conditionalFormattings
// element content.
func (r *decodeX14ConditionalFormattingRules) deleteCfRule(delCells map[int][][]int) ([]byte, error) {
var (
err error
condFmts = &xlsxX14ConditionalFormattings{}
)
for _, condFmt := range r.CondFmt {
if condFmt.Sqref, err = deleteCellsFromSqref(condFmt.Sqref, delCells); err != nil {
return nil, err
}
if len(condFmt.Sqref) == 0 {
continue
}
x14ConditionalFormatting := xlsxX14ConditionalFormatting{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
Pivot: condFmt.Pivot,
Sqref: condFmt.Sqref,
ExtLst: condFmt.ExtLst,
}
for _, rule := range condFmt.CfRule {
x14CfRule := &xlsxX14CfRule{
Type: rule.Type,
Priority: rule.Priority,
StopIfTrue: rule.StopIfTrue,
AboveAverage: rule.AboveAverage,
Percent: rule.Percent,
Bottom: rule.Bottom,
Operator: rule.Operator,
Text: rule.Text,
TimePeriod: rule.TimePeriod,
Rank: rule.Rank,
StdDev: rule.StdDev,
EqualAverage: rule.EqualAverage,
ActivePresent: rule.ActivePresent,
ID: rule.ID,
F: rule.F,
ColorScale: rule.ColorScale,
Dxf: rule.Dxf,
ExtLst: rule.ExtLst,
}
if rule.DataBar != nil {
x14DataBar := &xlsx14DataBar{
MaxLength: rule.DataBar.MaxLength,
MinLength: rule.DataBar.MinLength,
Border: rule.DataBar.Border,
ShowValue: rule.DataBar.ShowValue,
Direction: rule.DataBar.Direction,
BorderColor: rule.DataBar.BorderColor,
NegativeFillColor: rule.DataBar.NegativeFillColor,
AxisColor: rule.DataBar.AxisColor,
}
if rule.DataBar.Gradient != nil {
x14DataBar.Gradient = *rule.DataBar.Gradient
}
if rule.DataBar.Cfvo != nil {
for _, cfvo := range rule.DataBar.Cfvo {
x14DataBar.Cfvo = append(x14DataBar.Cfvo, &xlsxCfvo{
Gte: cfvo.Gte,
Type: cfvo.Type,
Val: cfvo.Val,
ExtLst: cfvo.ExtLst,
})
}
}
x14CfRule.DataBar = x14DataBar
}
if rule.IconSet != nil {
x14IconSet := &xlsx14IconSet{
IconSet: rule.IconSet.IconSet,
ShowValue: rule.IconSet.ShowValue,
Percent: rule.IconSet.Percent,
Reverse: rule.IconSet.Reverse,
Custom: rule.IconSet.Custom,
CfIcon: rule.IconSet.CfIcon,
}
if rule.IconSet.Cfvo != nil {
for _, cfvo := range rule.IconSet.Cfvo {
x14IconSet.Cfvo = append(x14IconSet.Cfvo, &xlsx14Cfvo{
Type: cfvo.Type,
Gte: cfvo.Gte,
F: cfvo.F,
ExtLst: cfvo.ExtLst,
})
}
}
x14CfRule.IconSet = x14IconSet
}
x14ConditionalFormatting.CfRule = append(x14ConditionalFormatting.CfRule, x14CfRule)
}
condFmtBytes, _ := xml.Marshal(x14ConditionalFormatting)
condFmts.Content += string(condFmtBytes)
}
return xml.Marshal(condFmts)
}
// drawCondFmtCellIs provides a function to create conditional formatting rule
// for cell value (include between, not between, equal, not equal, greater
// than and less than) by given priority, criteria type and format settings.
func drawCondFmtCellIs(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
c := &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Operator: ct,
DxfID: format.Format,
}
// "between" and "not between" criteria require 2 values.
if ct == "between" || ct == "notBetween" {
c.Formula = append(c.Formula, []string{format.MinValue, format.MaxValue}...)
}
if inStrSlice(cellIsCriteriaType, ct, true) != -1 {
c.Formula = append(c.Formula, format.Value)
}
return c, nil
}
// drawCondFmtTimePeriod provides a function to create conditional formatting
// rule for time period by given priority, criteria type and format settings.
func drawCondFmtTimePeriod(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: "timePeriod",
Formula: []string{
map[string]string{
"yesterday": fmt.Sprintf("FLOOR(%s,1)=TODAY()-1", ref),
"today": fmt.Sprintf("FLOOR(%s,1)=TODAY()", ref),
"tomorrow": fmt.Sprintf("FLOOR(%s,1)=TODAY()+1", ref),
"last7Days": fmt.Sprintf("AND(TODAY()-FLOOR(%[1]s,1)<=6,FLOOR(%[1]s,1)<=TODAY())", ref),
"lastWeek": fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%[1]s,0)<(WEEKDAY(TODAY())+7))", ref),
"thisWeek": fmt.Sprintf("AND(TODAY()-ROUNDDOWN(%[1]s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%[1]s,0)-TODAY()>=7-WEEKDAY(TODAY()))", ref),
"nextWeek": fmt.Sprintf("AND(ROUNDDOWN(%[1]s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%[1]s,0)-TODAY()<(15-WEEKDAY(TODAY())))", ref),
"lastMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())-1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=1,YEAR(%[1]s)=YEAR(TODAY())-1)))", ref),
"thisMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY()),YEAR(%[1]s)=YEAR(TODAY()))", ref),
"nextMonth": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())+1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=12,YEAR(%[1]s)=YEAR(TODAY())+1)))", ref),
}[ct],
},
DxfID: format.Format,
}, nil
}
// drawCondFmtText provides a function to create conditional formatting rule for
// text cell values by given priority, criteria type and format settings.
func drawCondFmtText(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: map[string]string{
"containsText": "containsText",
"notContains": "notContainsText",
"beginsWith": "beginsWith",
"endsWith": "endsWith",
}[ct],
Text: format.Value,
Operator: ct,
Formula: []string{
map[string]string{
"containsText": fmt.Sprintf("NOT(ISERROR(SEARCH(\"%s\",%s)))",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"notContains": fmt.Sprintf("ISERROR(SEARCH(\"%s\",%s))",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"beginsWith": fmt.Sprintf("LEFT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"endsWith": fmt.Sprintf("RIGHT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
}[ct],
},
DxfID: format.Format,
}, nil
}
// drawCondFmtTop10 provides a function to create conditional formatting rule
// for top N (default is top 10) by given priority, criteria type and format
// settings.
func drawCondFmtTop10(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
c := &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Bottom: format.Type == "bottom",
Type: validType[format.Type],
Rank: 10,
DxfID: format.Format,
Percent: format.Percent,
}
if rank, err := strconv.Atoi(format.Value); err == nil {
c.Rank = rank
}
return c, nil
}
// drawCondFmtAboveAverage provides a function to create conditional
// formatting rule for above average and below average by given priority,
// criteria type and format settings.
func drawCondFmtAboveAverage(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
AboveAverage: boolPtr(format.AboveAverage),
DxfID: format.Format,
}, nil
}
// drawCondFmtDuplicateUniqueValues provides a function to create conditional
// formatting rule for duplicate and unique values by given priority, criteria
// type and format settings.
func drawCondFmtDuplicateUniqueValues(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
DxfID: format.Format,
}, nil
}
// drawCondFmtColorScale provides a function to create conditional formatting
// rule for color scale (include 2 color scale and 3 color scale) by given
// priority, criteria type and format settings.
func drawCondFmtColorScale(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
minValue := format.MinValue
if minValue == "" {
minValue = "0"
}
maxValue := format.MaxValue
if maxValue == "" {
maxValue = "0"
}
midValue := format.MidValue
if midValue == "" {
midValue = "50"
}
c := &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: "colorScale",
ColorScale: &xlsxColorScale{
Cfvo: []*xlsxCfvo{
{Type: format.MinType, Val: minValue},
},
Color: []*xlsxColor{
{RGB: getPaletteColor(format.MinColor)},
},
},
}
if validType[format.Type] == "3_color_scale" {
c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MidType, Val: midValue})
c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MidColor)})
}
c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MaxType, Val: maxValue})
c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MaxColor)})
return c, nil
}
// drawCondFmtDataBar provides a function to create conditional formatting
// rule for data bar by given priority, criteria type and format settings.
func drawCondFmtDataBar(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
var x14CfRule *xlsxX14CfRule
var extLst *xlsxExtLst
if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" {
extLst = &xlsxExtLst{Ext: fmt.Sprintf(`%s`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)}
x14CfRule = &xlsxX14CfRule{
Type: validType[format.Type],
ID: GUID,
DataBar: &xlsx14DataBar{
MaxLength: 100,
Border: format.BarBorderColor != "",
Gradient: !format.BarSolid,
Direction: format.BarDirection,
Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}},
NegativeFillColor: &xlsxColor{RGB: "FFFF0000"},
AxisColor: &xlsxColor{RGB: "FFFF0000"},
},
}
if x14CfRule.DataBar.Border {
x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)}
}
}
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
DataBar: &xlsxDataBar{
ShowValue: boolPtr(!format.BarOnly),
Cfvo: []*xlsxCfvo{{Type: format.MinType, Val: format.MinValue}, {Type: format.MaxType, Val: format.MaxValue}},
Color: []*xlsxColor{{RGB: getPaletteColor(format.BarColor)}},
},
ExtLst: extLst,
}, x14CfRule
}
// drawCondFmtExp provides a function to create conditional formatting rule
// for expression by given priority, criteria type and format settings.
func drawCondFmtExp(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{format.Criteria},
DxfID: format.Format,
}, nil
}
// drawCondFmtErrors provides a function to create conditional formatting rule
// for cells with errors by given priority, criteria type and format settings.
func drawCondFmtErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("ISERROR(%s)", ref)},
DxfID: format.Format,
}, nil
}
// drawCondFmtNoErrors provides a function to create conditional formatting rule
// for cells without errors by given priority, criteria type and format settings.
func drawCondFmtNoErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("NOT(ISERROR(%s))", ref)},
DxfID: format.Format,
}, nil
}
// drawCondFmtBlanks provides a function to create conditional formatting rule
// for blank cells by given priority, criteria type and format settings.
func drawCondFmtBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))=0", ref)},
DxfID: format.Format,
}, nil
}
// drawCondFmtNoBlanks provides a function to create conditional formatting rule
// for no blanks cells by given priority, criteria type and format settings.
func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))>0", ref)},
DxfID: format.Format,
}, nil
}
// drawCondFmtIconSet provides a function to create conditional formatting rule
// for icon set by given priority, criteria type and format settings.
func drawCondFmtIconSet(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
if cfRule, ok := condFmtIconSetPresets[format.IconStyle]; ok {
cfRule.Priority = p + 1
cfRule.IconSet.IconSet = format.IconStyle
cfRule.IconSet.Reverse = format.ReverseIcons
cfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly)
cfRule.Type = validType[format.Type]
return cfRule, nil
}
if x14CfRule, ok := condFmtX14IconSetPresets[format.IconStyle]; ok {
x14CfRule.Type = validType[format.Type]
x14CfRule.Priority = p + 1
x14CfRule.StopIfTrue = format.StopIfTrue
x14CfRule.AboveAverage = boolPtr(format.AboveAverage)
x14CfRule.Percent = format.Percent
x14CfRule.ID = GUID
x14CfRule.IconSet.IconSet = format.IconStyle
x14CfRule.IconSet.Reverse = format.ReverseIcons
x14CfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly)
return nil, x14CfRule
}
return nil, nil
}
// getPaletteColor provides a function to convert the RBG color by given
// string.
func getPaletteColor(color string) string {
return "FF" + strings.ReplaceAll(strings.ToUpper(color), "#", "")
}
// themeReader provides a function to get the pointer to the xl/theme/theme1.xml
// structure after deserialization.
func (f *File) themeReader() (*decodeTheme, error) {
if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok {
return nil, nil
}
theme := decodeTheme{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))).
Decode(&theme); err != nil && err != io.EOF {
return &theme, err
}
return &theme, nil
}
// ThemeColor applied the color with tint value.
func ThemeColor(baseColor string, tint float64) string {
if tint == 0 {
return "FF" + baseColor
}
r, _ := strconv.ParseUint(baseColor[:2], 16, 64)
g, _ := strconv.ParseUint(baseColor[2:4], 16, 64)
b, _ := strconv.ParseUint(baseColor[4:6], 16, 64)
var h, s, l float64
if r <= math.MaxUint8 && g <= math.MaxUint8 && b <= math.MaxUint8 {
h, s, l = RGBToHSL(uint8(r), uint8(g), uint8(b))
}
if tint < 0 {
l *= 1 + tint
} else {
l = l*(1-tint) + (1 - (1 - tint))
}
br, bg, bb := HSLToRGB(h, s, l)
return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb)
}
================================================
FILE: styles_test.go
================================================
package excelize
import (
"fmt"
"math"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStyleFill(t *testing.T) {
cases := []struct {
label string
format *Style
expectFill bool
}{{
label: "no_fill",
format: &Style{Alignment: &Alignment{WrapText: true}},
expectFill: false,
}, {
label: "fill",
format: &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}},
expectFill: true,
}}
for _, testCase := range cases {
xl := NewFile()
styleID, err := xl.NewStyle(testCase.format)
assert.NoError(t, err)
styles, err := xl.stylesReader()
assert.NoError(t, err)
style := styles.CellXfs.Xf[styleID]
if testCase.expectFill {
assert.NotEqual(t, *style.FillID, 0, testCase.label)
} else {
assert.Equal(t, *style.FillID, 0, testCase.label)
}
}
f := NewFile()
styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}})
assert.NoError(t, err)
styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"000000"}}})
assert.NoError(t, err)
assert.Equal(t, styleID1, styleID2)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx")))
}
func TestSetConditionalFormat(t *testing.T) {
cases := []struct {
label string
format []ConditionalFormatOptions
rules []*xlsxCfRule
}{{
label: "3_color_scale",
format: []ConditionalFormatOptions{{
Type: "3_color_scale",
Criteria: "=",
MinType: "num",
MidType: "num",
MaxType: "num",
MinValue: "-10",
MidValue: "0",
MaxValue: "10",
MinColor: "ff0000",
MidColor: "00ff00",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
ColorScale: &xlsxColorScale{
Cfvo: []*xlsxCfvo{{
Type: "num",
Val: "-10",
}, {
Type: "num",
Val: "0",
}, {
Type: "num",
Val: "10",
}},
Color: []*xlsxColor{{
RGB: "FFFF0000",
}, {
RGB: "FF00FF00",
}, {
RGB: "FF0000FF",
}},
},
}},
}, {
label: "3_color_scale default min/mid/max",
format: []ConditionalFormatOptions{{
Type: "3_color_scale",
Criteria: "=",
MinType: "num",
MidType: "num",
MaxType: "num",
MinColor: "ff0000",
MidColor: "00ff00",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
ColorScale: &xlsxColorScale{
Cfvo: []*xlsxCfvo{{
Type: "num",
Val: "0",
}, {
Type: "num",
Val: "50",
}, {
Type: "num",
Val: "0",
}},
Color: []*xlsxColor{{
RGB: "FFFF0000",
}, {
RGB: "FF00FF00",
}, {
RGB: "FF0000FF",
}},
},
}},
}, {
label: "2_color_scale default min/max",
format: []ConditionalFormatOptions{{
Type: "2_color_scale",
Criteria: "=",
MinType: "num",
MaxType: "num",
MinColor: "ff0000",
MaxColor: "0000ff",
}},
rules: []*xlsxCfRule{{
Priority: 1,
Type: "colorScale",
ColorScale: &xlsxColorScale{
Cfvo: []*xlsxCfvo{{
Type: "num",
Val: "0",
}, {
Type: "num",
Val: "0",
}},
Color: []*xlsxColor{{
RGB: "FFFF0000",
}, {
RGB: "FF0000FF",
}},
},
}},
}}
for _, testCase := range cases {
f := NewFile()
const sheet = "Sheet1"
const rangeRef = "A1:A1"
assert.NoError(t, f.SetConditionalFormat(sheet, rangeRef, testCase.format))
ws, err := f.workSheetReader(sheet)
assert.NoError(t, err)
cf := ws.ConditionalFormatting
assert.Len(t, cf, 1, testCase.label)
assert.Len(t, cf[0].CfRule, 1, testCase.label)
assert.Equal(t, rangeRef, cf[0].SQRef, testCase.label)
assert.EqualValues(t, testCase.rules, cf[0].CfRule, testCase.label)
}
// Test creating a conditional format with a solid color data bar style
f := NewFile()
condFmts := []ConditionalFormatOptions{
{Type: "data_bar", BarColor: "#A9D08E", BarSolid: true, Format: intPtr(0), Criteria: "=", MinType: "min", MaxType: "max"},
}
for _, ref := range []string{"A1:A2", "B1:B2"} {
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
}
f = NewFile()
// Test creating a conditional format without cell reference
assert.Equal(t, ErrParameterRequired, f.SetConditionalFormat("Sheet1", "", nil))
// Test creating a conditional format with invalid cell reference
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2:A3", nil))
// Test creating a conditional format with existing extension lists
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURISlicerListX14, ExtURISparklineGroups)}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}}))
f = NewFile()
// Test creating a conditional format with invalid extension list characters
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: ""}
assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element closed by ")
// Test creating a conditional format with invalid icon set style
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "unknown"}}))
// Test unsupported conditional formatting rule types
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: "unsupported"}}))
t.Run("multi_conditional_formatting_rules_priority", func(t *testing.T) {
f := NewFile()
var condFmts []ConditionalFormatOptions
for _, color := range []string{
"#264B96", // Blue
"#F9A73E", // Yellow
"#006F3C", // Green
} {
condFmts = append(condFmts, ConditionalFormatOptions{
Type: "data_bar",
Criteria: "=",
MinType: "num",
MaxType: "num",
MinValue: "0",
MaxValue: "5",
BarColor: color,
BarSolid: true,
})
}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A5", condFmts))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "B1:B5", condFmts))
for r := 1; r <= 20; r++ {
cell, err := CoordinatesToCellName(1, r)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", cell, r))
cell, err = CoordinatesToCellName(2, r)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", cell, r))
}
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
var priorities []int
expected := []int{1, 2, 3, 4, 5, 6}
for _, condFmt := range ws.(*xlsxWorksheet).ConditionalFormatting {
for _, rule := range condFmt.CfRule {
priorities = append(priorities, rule.Priority)
}
}
assert.Equal(t, expected, priorities)
assert.NoError(t, f.Close())
})
}
func TestGetConditionalFormats(t *testing.T) {
f := NewFile()
idx, err := f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
for idx, format := range [][]ConditionalFormatOptions{
{{Type: "cell", Format: &idx, Criteria: "greater than", Value: "6"}},
{{Type: "cell", Format: &idx, Criteria: "between", MinValue: "6", MaxValue: "8"}},
{{Type: "time_period", Format: &idx, Criteria: "yesterday"}},
{{Type: "time_period", Format: &idx, Criteria: "today"}},
{{Type: "time_period", Format: &idx, Criteria: "tomorrow"}},
{{Type: "time_period", Format: &idx, Criteria: "last 7 days"}},
{{Type: "time_period", Format: &idx, Criteria: "last week"}},
{{Type: "time_period", Format: &idx, Criteria: "this week"}},
{{Type: "time_period", Format: &idx, Criteria: "continue week"}},
{{Type: "time_period", Format: &idx, Criteria: "last month"}},
{{Type: "time_period", Format: &idx, Criteria: "this month"}},
{{Type: "time_period", Format: &idx, Criteria: "continue month"}},
{{Type: "text", Format: &idx, Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}},
{{Type: "text", Format: &idx, Criteria: "not containing", Value: "text"}},
{{Type: "text", Format: &idx, Criteria: "begins with", Value: "prefix"}},
{{Type: "text", Format: &idx, Criteria: "ends with", Value: "suffix"}},
{{Type: "top", Format: &idx, Criteria: "=", Value: "6"}},
{{Type: "bottom", Format: &idx, Criteria: "=", Value: "6"}},
{{Type: "average", AboveAverage: true, Format: &idx, Criteria: "="}},
{{Type: "duplicate", Format: &idx, Criteria: "="}},
{{Type: "unique", Format: &idx, Criteria: "="}},
{{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}},
{{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
{{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "formula", Format: &idx, Criteria: "1"}},
{{Type: "blanks", Format: &idx}},
{{Type: "no_blanks", Format: &idx}},
{{Type: "errors", Format: &idx}},
{{Type: "no_errors", Format: &idx}},
{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
{{Type: "icon_set", IconStyle: "3Stars", ReverseIcons: true, IconsOnly: true}},
{{Type: "icon_set", IconStyle: "3Triangles", ReverseIcons: true, IconsOnly: true}},
{{Type: "icon_set", IconStyle: "5Boxes", ReverseIcons: true, IconsOnly: true}},
} {
col, err := ColumnNumberToName(idx + 2)
assert.NoError(t, err)
err = f.SetConditionalFormat("Sheet1", fmt.Sprintf("%s1:%s10", col, col), format)
assert.NoError(t, err)
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Equal(t, format, opts[fmt.Sprintf("%s1:%s10", col, col)])
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetConditionalFormats.xlsx")))
// Test unset all conditional formats
f, err = OpenFile(filepath.Join("test", "TestGetConditionalFormats.xlsx"))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"C1"},
Range: []string{"Sheet1!A1:B1"},
}))
for _, rangeRef := range []string{"Y1:Y10", "Z1:Z10", "AG1:AG10", "AH1:AH10", "AI1:AI10"} {
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", rangeRef))
}
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test unset conditional formats with invalid extension list characters
f, err = OpenFile(filepath.Join("test", "TestGetConditionalFormats.xlsx"))
assert.NoError(t, err)
w, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
w.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIConditionalFormattings)}
assert.EqualError(t, f.UnsetConditionalFormat("Sheet1", "Y1:Y10"), "XML syntax error on line 1: element closed by ")
assert.NoError(t, f.Close())
// Test get multiple conditional formats
f = NewFile()
expected := []ConditionalFormatOptions{
{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true},
{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: false, StopIfTrue: true},
}
err = f.SetConditionalFormat("Sheet1", "A2:A1,B:B,2:2", expected)
assert.NoError(t, err)
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, opts["A2:A1 B1:B1048576 A2:XFD2"])
f = NewFile()
// Test get conditional formats on no exists worksheet
_, err = f.GetConditionalFormats("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get conditional formats with invalid sheet name
_, err = f.GetConditionalFormats("Sheet:1")
assert.Equal(t, ErrSheetNameInvalid, err)
// Test get conditional formats with invalid extension list characters
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURIConditionalFormattings)}
_, err = f.GetConditionalFormats("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: element closed by ")
}
func TestUnsetConditionalFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: &format, Value: "6"}}))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
// Test unset conditional format with invalid range
assert.Equal(t, f.UnsetConditionalFormat("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
// Test unset conditional format on not exists worksheet
assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist")
// Test unset conditional format with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.UnsetConditionalFormat("Sheet:1", "A1:A10"))
// Test unset conditional format from extLst
assert.NoError(t, f.SetConditionalFormat("Sheet1", "B1:B10", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "3Stars"}}))
assert.NoError(t, f.SetConditionalFormat("Sheet1", "C1:C10", []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "5Boxes"}}))
condFmts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, condFmts, 2)
// Unset the first extLst conditional format
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "B1:B10"))
condFmts, err = f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, condFmts, 1)
assert.NotNil(t, condFmts["C1:C10"])
// Unset the last extLst conditional format
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "C1:C10"))
condFmts, err = f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, condFmts, 0)
// Save spreadsheet by the given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnsetConditionalFormat.xlsx")))
// Test get and unset conditional format with invalid sqref value
f = NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws.ConditionalFormatting = []*xlsxConditionalFormatting{{SQRef: ""}}
_, err = f.GetConditionalFormats("Sheet1")
assert.Equal(t, ErrParameterRequired, err)
ws.ConditionalFormatting = []*xlsxConditionalFormatting{{SQRef: "A"}}
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.UnsetConditionalFormat("Sheet1", "A1"))
// Test unset conditional formats with invalid extension list characters
ws.ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`
A`, ExtURIConditionalFormattings)}
assert.Equal(t, f.UnsetConditionalFormat("Sheet1", "A1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
t.Run("with_unordered_sqref", func(t *testing.T) {
f := NewFile()
condFmt := []ConditionalFormatOptions{{Type: "cell", Criteria: "greater than", Value: "6"}}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A5:A10 A15:A20 A3:A4", condFmt))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A7"))
condFmts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, condFmts, 1)
assert.Equal(t, condFmt, condFmts["A3:A6 A8:A10 A15:A20"])
})
t.Run("with_unordered_sqref_in_extLst", func(t *testing.T) {
f := NewFile()
condFmt := []ConditionalFormatOptions{{Type: "icon_set", IconStyle: "3Stars"}}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A5:A10 A15:A20 A3:A4", condFmt))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A7"))
condFmts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)
assert.Len(t, condFmts, 1)
assert.Equal(t, condFmt, condFmts["A3:A6 A8:A10 A15:A20"])
})
}
func TestNewStyle(t *testing.T) {
f := NewFile()
for i := range 16 {
_, err := f.NewStyle(&Style{
Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: i},
})
assert.NoError(t, err)
}
_, err := f.NewStyle(&Style{
Fill: Fill{Type: "solid"},
})
assert.Equal(t, ErrFillType, err)
_, err = f.NewStyle(&Style{
Fill: Fill{Type: "gradient", Color: []string{"FFFFFF"}, Shading: 0},
})
assert.Equal(t, ErrFillGradientColor, err)
_, err = f.NewStyle(&Style{
Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: 17},
})
assert.Equal(t, ErrFillGradientShading, err)
_, err = f.NewStyle(&Style{
Fill: Fill{Type: "pattern", Color: []string{"FFFFFF", "4E71BE"}, Shading: 0},
})
assert.Equal(t, ErrFillPatternColor, err)
_, err = f.NewStyle(&Style{
Fill: Fill{Type: "pattern", Pattern: 19},
})
assert.Equal(t, ErrFillPattern, err)
f = NewFile()
styleID, err := f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "777777"}})
assert.NoError(t, err)
styles, err := f.stylesReader()
assert.NoError(t, err)
fontID := styles.CellXfs.Xf[styleID].FontID
font := styles.Fonts.Font[*fontID]
assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name")
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
_, err = f.NewStyle(&Style{})
assert.NoError(t, err)
_, err = f.NewStyle(nil)
assert.NoError(t, err)
// Test gradient fills
f = NewFile()
styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "4E71BE"}, Shading: 1, Pattern: 1}})
assert.NoError(t, err)
styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FF0000", "4E71BE"}, Shading: 1, Pattern: 1}})
assert.NoError(t, err)
assert.NotEqual(t, styleID1, styleID2)
var exp string
f = NewFile()
_, err = f.NewStyle(&Style{CustomNumFmt: &exp})
assert.Equal(t, ErrCustomNumFmt, err)
_, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}})
assert.Equal(t, ErrFontLength, err)
_, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}})
assert.Equal(t, ErrFontSize, err)
// Test create numeric custom style
numFmt := "####;####"
f.Styles.NumFmts = nil
styleID, err = f.NewStyle(&Style{
CustomNumFmt: &numFmt,
})
assert.NoError(t, err)
assert.Equal(t, 1, styleID)
assert.NotNil(t, f.Styles)
assert.NotNil(t, f.Styles.CellXfs)
assert.NotNil(t, f.Styles.CellXfs.Xf)
nf := f.Styles.CellXfs.Xf[styleID]
assert.Equal(t, 164, *nf.NumFmtID)
// Test create currency custom style
f.Styles.NumFmts = nil
styleID, err = f.NewStyle(&Style{
NumFmt: 32, // must not be in currencyNumFmt
})
assert.NoError(t, err)
assert.Equal(t, 2, styleID)
assert.NotNil(t, f.Styles)
assert.NotNil(t, f.Styles.CellXfs)
assert.NotNil(t, f.Styles.CellXfs.Xf)
nf = f.Styles.CellXfs.Xf[styleID]
assert.Equal(t, 32, *nf.NumFmtID)
// Test set build-in scientific number format
styleID, err = f.NewStyle(&Style{NumFmt: 11})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", styleID))
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]float64{1.23, 1.234}))
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows)
f = NewFile()
// Test currency number format
customNumFmt := "[$$-409]#,##0.00"
style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
style2, err := f.NewStyle(&Style{NumFmt: 165})
assert.NoError(t, err)
assert.Equal(t, style1, style2)
style3, err := f.NewStyle(&Style{NumFmt: 166})
assert.NoError(t, err)
assert.Equal(t, 2, style3)
f = NewFile()
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = nil
style4, err := f.NewStyle(&Style{NumFmt: 160})
assert.NoError(t, err)
assert.Equal(t, 0, style4)
f = NewFile()
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = nil
style5, err := f.NewStyle(&Style{NumFmt: 160})
assert.NoError(t, err)
assert.Equal(t, 0, style5)
// Test create style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.NewStyle(&Style{NumFmt: 165})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test create cell styles reach maximum
f = NewFile()
f.Styles.CellXfs.Xf = make([]xlsxXf, MaxCellStyles)
f.Styles.CellXfs.Count = MaxCellStyles
_, err = f.NewStyle(&Style{NumFmt: 0})
assert.Equal(t, ErrCellStyles, err)
t.Run("for_create_new_style_with_font_charset", func(t *testing.T) {
f, charset := NewFile(), 178
style := &Style{Font: &Font{
Family: "B Titr",
Size: 12,
Color: "000000",
Charset: &charset,
}}
styleID, err := f.NewStyle(style)
assert.NoError(t, err)
assert.Equal(t, 1, styleID)
s, err := f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, style.Font, s.Font)
text := "\u0627\u06cc\u0646\u0020\u06cc\u06a9\u0020\u0645\u062a\u0646\u0020\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc\u0020\u0627\u0633\u062a\u002e"
assert.NoError(t, f.SetCellValue("Sheet1", "A1", text))
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetFontCharset.xlsx")))
})
t.Run("for_recreate_default_style", func(t *testing.T) {
f := NewFile()
style, err := f.GetStyle(0)
assert.NoError(t, err)
styleID, err := f.NewStyle(style)
assert.NoError(t, err)
assert.Equal(t, 0, styleID)
})
}
func TestConditionalStyle(t *testing.T) {
f := NewFile()
expected := &Style{Protection: &Protection{Hidden: true, Locked: true}}
idx, err := f.NewConditionalStyle(expected)
assert.NoError(t, err)
style, err := f.GetConditionalStyle(idx)
assert.NoError(t, err)
assert.Equal(t, expected, style)
_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(4), NumFmt: 165, NegRed: true})
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(-1)})
assert.NoError(t, err)
expected = &Style{NumFmt: 1}
idx, err = f.NewConditionalStyle(expected)
assert.NoError(t, err)
style, err = f.GetConditionalStyle(idx)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)
assert.Zero(t, *style.DecimalPlaces)
_, err = f.NewConditionalStyle(&Style{NumFmt: 27})
assert.NoError(t, err)
numFmt := "general"
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt})
assert.NoError(t, err)
numFmt1 := "0.00"
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt1})
assert.NoError(t, err)
// Test create conditional style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get conditional style with invalid style index
_, err = f.GetConditionalStyle(1)
assert.Equal(t, newInvalidStyleID(1), err)
// Test get conditional style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.GetConditionalStyle(1)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
// Test get conditional style with background color and empty pattern type
idx, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.NoError(t, err)
f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.PatternType = ""
f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.FgColor = nil
f.Styles.Dxfs.Dxfs[0].Fill.PatternFill.BgColor = &xlsxColor{Theme: intPtr(6)}
style, err = f.GetConditionalStyle(idx)
assert.NoError(t, err)
assert.Equal(t, "pattern", style.Fill.Type)
assert.Equal(t, []string{"A5A5A5"}, style.Fill.Color)
}
func TestGetDefaultFont(t *testing.T) {
f := NewFile()
s, err := f.GetDefaultFont()
assert.NoError(t, err)
assert.Equal(t, s, "Calibri", "Default font should be Calibri")
// Test get default font with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.GetDefaultFont()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDefaultFont(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetDefaultFont("Arial"))
styles, err := f.stylesReader()
assert.NoError(t, err)
s, err := f.GetDefaultFont()
assert.NoError(t, err)
assert.Equal(t, s, "Arial", "Default font should change to Arial")
assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true)
// Test set default font with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDefaultFont("Arial"), "XML syntax error on line 1: invalid UTF-8")
}
func TestStylesReader(t *testing.T) {
f := NewFile()
// Test read styles with unsupported charset
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
styles, err := f.stylesReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualValues(t, new(xlsxStyleSheet), styles)
}
func TestThemeReader(t *testing.T) {
f := NewFile()
// Test read theme with unsupported charset
f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset)
theme, err := f.themeReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualValues(t, &decodeTheme{}, theme)
}
func TestSetCellStyle(t *testing.T) {
f := NewFile()
// Test set cell style on not exists worksheet
assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN does not exist")
// Test set cell style with invalid style ID
assert.Equal(t, newInvalidStyleID(-1), f.SetCellStyle("Sheet1", "A1", "A2", -1))
// Test set cell style with not exists style ID
assert.Equal(t, newInvalidStyleID(10), f.SetCellStyle("Sheet1", "A1", "A2", 10))
// Test set cell style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetStyleID(t *testing.T) {
f := NewFile()
styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil)
assert.NoError(t, err)
assert.Equal(t, -1, styleID)
}
func TestGetFillID(t *testing.T) {
styles, err := NewFile().stylesReader()
assert.NoError(t, err)
assert.Equal(t, -1, getFillID(styles, &Style{Fill: Fill{Type: "unknown"}}))
}
func TestThemeColor(t *testing.T) {
for _, clr := range [][]string{
{"FF000000", ThemeColor("000000", -0.1)},
{"FF000000", ThemeColor("000000", 0)},
{"FF33FF33", ThemeColor("00FF00", 0.2)},
{"FFFFFFFF", ThemeColor("000000", 1)},
{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(math.MaxUint8+1)), 6), 1)},
{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(-1)), 6), 1)},
} {
assert.Equal(t, clr[0], clr[1])
}
}
func TestGetNumFmtID(t *testing.T) {
f := NewFile()
fs1, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 10})
assert.NoError(t, err)
id1 := getNumFmtID(&xlsxStyleSheet{}, fs1)
fs2, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 0})
assert.NoError(t, err)
id2 := getNumFmtID(&xlsxStyleSheet{}, fs2)
assert.NotEqual(t, id1, id2)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleNumFmt.xlsx")))
}
func TestGetThemeColor(t *testing.T) {
assert.Empty(t, (&File{}).getThemeColor(&xlsxColor{}))
f := NewFile()
assert.Empty(t, f.getThemeColor(nil))
var theme int
assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{Theme: &theme}))
assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{RGB: "FFFFFF"}))
assert.Equal(t, "FF8080", f.getThemeColor(&xlsxColor{Indexed: 2, Tint: 0.5}))
assert.Empty(t, f.getThemeColor(&xlsxColor{Indexed: len(IndexedColorMapping), Tint: 0.5}))
clr := &decodeCTColor{}
assert.Nil(t, clr.colorChoice())
}
func TestGetStyle(t *testing.T) {
f := NewFile()
expected := &Style{
Border: []Border{
{Type: "left", Color: "0000FF", Style: 3},
{Type: "right", Color: "FF0000", Style: 6},
{Type: "top", Color: "00FF00", Style: 4},
{Type: "bottom", Color: "FFFF00", Style: 5},
{Type: "diagonalUp", Color: "A020F0", Style: 7},
{Type: "diagonalDown", Color: "A020F0", Style: 7},
},
Fill: Fill{Type: "gradient", Shading: 16, Color: []string{"0000FF", "00FF00"}},
Font: &Font{
Bold: true, Italic: true, Underline: "single", Family: "Arial",
Size: 8.5, Strike: true, Color: "777777", ColorIndexed: 1,
ColorTint: 0.1, VertAlign: "superscript",
},
Alignment: &Alignment{
Horizontal: "center",
Indent: 1,
JustifyLastLine: true,
ReadingOrder: 1,
RelativeIndent: 1,
ShrinkToFit: true,
TextRotation: 180,
Vertical: "center",
WrapText: true,
},
Protection: &Protection{Hidden: true, Locked: true},
NumFmt: 49,
}
styleID, err := f.NewStyle(expected)
assert.NoError(t, err)
style, err := f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.Border, style.Border)
assert.Equal(t, expected.Fill, style.Fill)
assert.Equal(t, expected.Font, style.Font)
assert.Equal(t, expected.Alignment, style.Alignment)
assert.Equal(t, expected.Protection, style.Protection)
assert.Equal(t, expected.NumFmt, style.NumFmt)
assert.Nil(t, style.DecimalPlaces)
expected = &Style{
Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"0000FF"}},
}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.Fill, style.Fill)
assert.Nil(t, style.DecimalPlaces)
expected = &Style{NumFmt: 2}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)
assert.Equal(t, 2, *style.DecimalPlaces)
expected = &Style{NumFmt: 27}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)
assert.Nil(t, style.DecimalPlaces)
expected = &Style{NumFmt: 165}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)
assert.Equal(t, 2, *style.DecimalPlaces)
decimal := 4
expected = &Style{NumFmt: 165, DecimalPlaces: &decimal, NegRed: true}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, 0, style.NumFmt)
assert.Equal(t, *expected.DecimalPlaces, *style.DecimalPlaces)
assert.Equal(t, "[$$-409]#,##0.0000;[Red][$$-409]#,##0.0000", *style.CustomNumFmt)
for _, val := range [][]interface{}{
{"$#,##0", 0},
{"$#,##0.0", 1},
{"_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", 0},
{"_($* #,##000_);_($* (#,##000);_($* \"-\"_);_(@_)", 0},
{"_($* #,##0.0000_);_($* (#,##0.0000);_($* \"-\"????_);_(@_)", 4},
} {
numFmtCode := val[0].(string)
expected = &Style{CustomNumFmt: &numFmtCode}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, val[1].(int), *style.DecimalPlaces, numFmtCode)
}
for _, val := range []string{
";$#,##0",
";$#,##0;",
";$#,##0.0",
";$#,##0.0;",
"$#,##0;0.0",
"_($* #,##0_);;_($* \"-\"_);_(@_)",
"_($* #,##0.0_);_($* (#,##0.00);_($* \"-\"_);_(@_)",
} {
expected = &Style{CustomNumFmt: &val}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Nil(t, style.DecimalPlaces)
}
// Test get style with custom color index
f.Styles.Colors = &xlsxStyleColors{
IndexedColors: &xlsxIndexedColors{
RgbColor: []xlsxColor{{RGB: "FF012345"}},
},
}
assert.Equal(t, "012345", f.getThemeColor(&xlsxColor{Indexed: 0}))
f.Styles.Fonts.Font[0].U = &attrValString{}
f.Styles.CellXfs.Xf[0].FontID = intPtr(0)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, "single", style.Font.Underline)
// Test get style with invalid style index
style, err = f.GetStyle(-1)
assert.Nil(t, style)
assert.Equal(t, err, newInvalidStyleID(-1))
// Test get style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
style, err = f.GetStyle(1)
assert.Nil(t, style)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
================================================
FILE: table.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"regexp"
"strconv"
"strings"
)
var (
expressionFormat = regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
conditionFormat = regexp.MustCompile(`(or|\|\|)`)
blankFormat = regexp.MustCompile("blanks|nonblanks")
matchFormat = regexp.MustCompile("[*?]")
)
// parseTableOptions provides a function to parse the format settings of the
// table with default value.
func parseTableOptions(opts *Table) (*Table, error) {
var err error
if opts == nil {
return &Table{ShowRowStripes: boolPtr(true)}, err
}
if opts.ShowRowStripes == nil {
opts.ShowRowStripes = boolPtr(true)
}
if err = checkDefinedName(opts.Name); err != nil {
return opts, err
}
return opts, err
}
// AddTable provides the method to add table in a worksheet by given worksheet
// name, range reference and format set. For example, create a table of A1:D5
// on Sheet1:
//
// err := f.AddTable("Sheet1", &excelize.Table{Range: "A1:D5"})
//
// Create a table of F2:H6 on Sheet2 with format set:
//
// disable := false
// err := f.AddTable("Sheet2", &excelize.Table{
// Range: "F2:H6",
// Name: "table",
// StyleName: "TableStyleMedium2",
// ShowFirstColumn: true,
// ShowLastColumn: true,
// ShowRowStripes: &disable,
// ShowColumnStripes: true,
// })
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique, and must set the
// header row data of the table before calling the AddTable function. Multiple
// tables range reference that can't have an intersection.
//
// Name: The name of the table, in the same worksheet name of the table should
// be unique, starts with a letter or underscore (_), doesn't include a
// space or character, and should be no more than 255 characters
//
// StyleName: The built-in table style names
//
// TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11
func (f *File) AddTable(sheet string, table *Table) error {
options, err := parseTableOptions(table)
if err != nil {
return err
}
var exist bool
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/tables/table") {
var t xlsxTable
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return true
}
if exist = t.Name == options.Name; exist {
return false
}
}
return true
})
if exist {
return ErrExistsTableName
}
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
coordinates, err := rangeRefToCoordinates(options.Range)
if err != nil {
return err
}
// Correct table reference range, such correct C1:B3 to B1:C3.
_ = sortCoordinates(coordinates)
tableID := f.countTables() + 1
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
// Add first table for given sheet.
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
if err = f.addSheetTable(sheet, rID); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
f.clearCalcCache()
if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil {
return err
}
return f.addContentTypePart(tableID, "table")
}
// GetTables provides the method to get all tables in a worksheet by given
// worksheet name.
func (f *File) GetTables(sheet string) ([]Table, error) {
var tables []Table
ws, err := f.workSheetReader(sheet)
if err != nil {
return tables, err
}
if ws.TableParts == nil {
return tables, err
}
for _, tbl := range ws.TableParts.TableParts {
if tbl != nil {
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
tableXML := strings.ReplaceAll(target, "..", "xl")
content, ok := f.Pkg.Load(tableXML)
if !ok {
continue
}
var t xlsxTable
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return tables, err
}
table := Table{
rID: tbl.RID,
tID: t.ID,
tableXML: tableXML,
Range: t.Ref,
Name: t.Name,
}
if t.TableStyleInfo != nil {
table.StyleName = t.TableStyleInfo.Name
table.ShowColumnStripes = t.TableStyleInfo.ShowColumnStripes
table.ShowFirstColumn = t.TableStyleInfo.ShowFirstColumn
table.ShowLastColumn = t.TableStyleInfo.ShowLastColumn
table.ShowRowStripes = &t.TableStyleInfo.ShowRowStripes
}
tables = append(tables, table)
}
}
return tables, err
}
// DeleteTable provides the method to delete table by given table name.
func (f *File) DeleteTable(name string) error {
if err := checkDefinedName(name); err != nil {
return err
}
tbls, err := f.getTables()
if err != nil {
return err
}
f.clearCalcCache()
for sheet, tables := range tbls {
for _, table := range tables {
if table.Name != name {
continue
}
ws, _ := f.workSheetReader(sheet)
for i, tbl := range ws.TableParts.TableParts {
if tbl.RID == table.rID {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:i], ws.TableParts.TableParts[i+1:]...)
f.Pkg.Delete(table.tableXML)
_ = f.removeContentTypesPart(ContentTypeSpreadSheetMLTable, "/"+table.tableXML)
f.deleteSheetRelationships(sheet, tbl.RID)
break
}
}
if ws.TableParts.Count = len(ws.TableParts.TableParts); ws.TableParts.Count == 0 {
ws.TableParts = nil
}
return err
}
}
return newNoExistTableError(name)
}
// getTables provides a function to get all tables in a workbook.
func (f *File) getTables() (map[string][]Table, error) {
tables := map[string][]Table{}
for _, sheetName := range f.GetSheetList() {
tbls, err := f.GetTables(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return tables, err
}
tables[sheetName] = append(tables[sheetName], tbls...)
}
return tables, nil
}
// countTables provides a function to get table files count storage in the
// folder xl/tables.
func (f *File) countTables() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/tables/tableSingleCells") {
var cells xlsxSingleXMLCells
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
Decode(&cells); err != nil && err != io.EOF {
count++
return true
}
for _, cell := range cells.SingleXmlCell {
if count < cell.ID {
count = cell.ID
}
}
}
if strings.Contains(k.(string), "xl/tables/table") {
var t xlsxTable
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
count++
return true
}
if count < t.ID {
count = t.ID
}
}
return true
})
return count
}
// addSheetTable provides a function to add tablePart element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetTable(sheet string, rID int) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
table := &xlsxTablePart{
RID: "rId" + strconv.Itoa(rID),
}
if ws.TableParts == nil {
ws.TableParts = &xlsxTableParts{}
}
ws.TableParts.Count++
ws.TableParts.TableParts = append(ws.TableParts.TableParts, table)
return err
}
// setTableColumns provides a function to set cells value in header row for the
// table.
func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int, tbl *xlsxTable) error {
var (
idx int
header []string
tableColumns []*xlsxTableColumn
getTableColumn = func(name string) *xlsxTableColumn {
if tbl != nil && tbl.TableColumns != nil {
for _, column := range tbl.TableColumns.TableColumn {
if column.Name == name {
return column
}
}
}
return nil
}
)
for i := x1; i <= x2; i++ {
idx++
cell, err := CoordinatesToCellName(i, y1)
if err != nil {
return err
}
name, _ := f.GetCellValue(sheet, cell, Options{RawCellValue: true})
if _, err := strconv.Atoi(name); err == nil {
if showHeaderRow {
_ = f.SetCellStr(sheet, cell, name)
}
}
if name == "" || inStrSlice(header, name, true) != -1 {
name = "Column" + strconv.Itoa(idx)
if showHeaderRow {
_ = f.SetCellStr(sheet, cell, name)
}
}
header = append(header, name)
if column := getTableColumn(name); column != nil {
column.ID, column.DataDxfID, column.QueryTableFieldID = idx, 0, 0
tableColumns = append(tableColumns, column)
continue
}
tableColumns = append(tableColumns, &xlsxTableColumn{
ID: idx,
Name: name,
})
}
tbl.TableColumns = &xlsxTableColumns{
Count: len(tableColumns),
TableColumn: tableColumns,
}
return nil
}
// checkDefinedName check whether there are illegal characters in the defined
// name or table name. Verify that the name:
// 1. Starts with a letter or underscore (_)
// 2. Doesn't include a space or character that isn't allowed
func checkDefinedName(name string) error {
if countUTF16String(name) > MaxFieldLength {
return ErrNameLength
}
inCodeRange := func(code int, tbl []int) bool {
for i := 0; i < len(tbl); i += 2 {
if tbl[i] <= code && code <= tbl[i+1] {
return true
}
}
return false
}
for i, c := range name {
if i == 0 {
if inCodeRange(int(c), supportedDefinedNameAtStartCharCodeRange) {
continue
}
return newInvalidNameError(name)
}
if inCodeRange(int(c), supportedDefinedNameAfterStartCharCodeRange) {
continue
}
return newInvalidNameError(name)
}
return nil
}
// addTable provides a function to add table by given worksheet name,
// range reference and format set.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Table) error {
// Correct the minimum number of rows, the table at least two lines.
if y1 == y2 {
y2++
}
hideHeaderRow := opts != nil && opts.ShowHeaderRow != nil && !*opts.ShowHeaderRow
if hideHeaderRow {
y1++
}
// Correct table range reference, such correct C1:B3 to B1:C3.
ref, err := coordinatesToRangeRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}
name := opts.Name
if name == "" {
name = "Table" + strconv.Itoa(i)
}
t := xlsxTable{
XMLNS: NameSpaceSpreadSheet.Value,
ID: i,
Name: name,
DisplayName: name,
Ref: ref,
AutoFilter: &xlsxAutoFilter{
Ref: ref,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: opts.StyleName,
ShowFirstColumn: opts.ShowFirstColumn,
ShowLastColumn: opts.ShowLastColumn,
ShowRowStripes: *opts.ShowRowStripes,
ShowColumnStripes: opts.ShowColumnStripes,
},
}
_ = f.setTableColumns(sheet, !hideHeaderRow, x1, y1, x2, &t)
if hideHeaderRow {
t.AutoFilter = nil
t.HeaderRowCount = intPtr(0)
}
table, err := xml.Marshal(t)
f.saveFileList(tableXML, table)
return err
}
// AutoFilter provides the method to add auto filter in a worksheet by given
// worksheet name, range reference and settings. An auto filter in Excel is a
// way of filtering a 2D range of data based on some simple criteria. For
// example applying an auto filter to a cell range A1:D4 in the Sheet1:
//
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{})
//
// Filter data in an auto filter:
//
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{
// {Column: "B", Expression: "x != blanks"},
// })
//
// Column defines the filter columns in an auto filter range based on simple
// criteria
//
// It isn't sufficient to just specify the filter condition. You must also
// hide any rows that don't match the filter condition. Rows are hidden using
// the SetRowVisible function. Excelize can't filter rows automatically since
// this isn't part of the file format.
//
// Setting a filter criteria for a column:
//
// Expression defines the conditions, the following operators are available
// for setting the filter criteria:
//
// ==
// !=
// >
// <
// >=
// <=
// and
// or
//
// An expression can comprise a single statement or two statements separated
// by the 'and' and 'or' operators. For example:
//
// x < 2000
// x > 2000
// x == 2000
// x > 2000 and x < 5000
// x == 2000 or x == 5000
//
// Filtering of blank or non-blank data can be achieved by using a value of
// Blanks or NonBlanks in the expression:
//
// x == Blanks
// x == NonBlanks
//
// Excel also allows some simple string matching operations:
//
// x == b* // begins with b
// x != b* // doesn't begin with b
// x == *b // ends with b
// x != *b // doesn't end with b
// x == *b* // contains b
// x != *b* // doesn't contain b
//
// You can also use '*' to match any character or number and '?' to match any
// single character or number. No other regular expression quantifier is
// supported by Excel's filters. Excel's regular expression characters can be
// escaped using '~'.
//
// The placeholder variable x in the above examples can be replaced by any
// simple string. The actual placeholder name is ignored internally so the
// following are all equivalent:
//
// x < 2000
// col < 2000
// Price < 2000
func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) error {
coordinates, err := rangeRefToCoordinates(rangeRef)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
// Correct reference range, such correct C1:B3 to B1:C3.
ref, _ := coordinatesToRangeRef(coordinates, true)
wb, err := f.workbookReader()
if err != nil {
return err
}
sheetID, err := f.GetSheetIndex(sheet)
if err != nil {
return err
}
filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
d := xlsxDefinedName{
Name: builtInDefinedNames[3],
Hidden: true,
LocalSheetID: intPtr(sheetID),
Data: filterRange,
}
if wb.DefinedNames == nil {
wb.DefinedNames = &xlsxDefinedNames{
DefinedName: []xlsxDefinedName{d},
}
} else {
var definedNameExists bool
for idx := range wb.DefinedNames.DefinedName {
definedName, localSheetID := wb.DefinedNames.DefinedName[idx], 0
if definedName.LocalSheetID != nil {
localSheetID = *definedName.LocalSheetID
}
if definedName.Name == builtInDefinedNames[3] && localSheetID == sheetID && definedName.Hidden {
wb.DefinedNames.DefinedName[idx].Data = filterRange
definedNameExists = true
}
}
if !definedNameExists {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
}
}
columns := coordinates[2] - coordinates[0]
return f.autoFilter(sheet, ref, columns, coordinates[0], opts)
}
// autoFilter provides a function to extract the tokens from the filter
// expression. The tokens are mainly non-whitespace groups.
func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilterOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.SheetPr != nil {
ws.SheetPr.FilterMode = true
}
ws.SheetPr = &xlsxSheetPr{FilterMode: true}
filter := &xlsxAutoFilter{
Ref: ref,
}
ws.AutoFilter = filter
for _, opt := range opts {
if opt.Column == "" || opt.Expression == "" {
continue
}
fsCol, err := ColumnNameToNumber(opt.Column)
if err != nil {
return err
}
offset := fsCol - col
if offset < 0 || offset > columns {
return newInvalidAutoFilterColumnError(opt.Column)
}
fc := &xlsxFilterColumn{ColID: offset}
token := expressionFormat.FindAllString(opt.Expression, -1)
if len(token) != 3 && len(token) != 7 {
return newInvalidAutoFilterExpError(opt.Expression)
}
expressions, tokens, err := f.parseFilterExpression(opt.Expression, token)
if err != nil {
return err
}
f.writeAutoFilter(fc, expressions, tokens)
filter.FilterColumn = append(filter.FilterColumn, fc)
}
ws.AutoFilter = filter
return nil
}
// writeAutoFilter provides a function to check for single or double custom
// filters as default filters and handle them accordingly.
func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string) {
if len(exp) == 1 && exp[0] == 2 {
// Single equality.
var filters []*xlsxFilter
filters = append(filters, &xlsxFilter{Val: tokens[0]})
fc.Filters = &xlsxFilters{Filter: filters}
return
}
if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
// Double equality with "or" operator.
var filters []*xlsxFilter
for _, v := range tokens {
filters = append(filters, &xlsxFilter{Val: v})
}
fc.Filters = &xlsxFilters{Filter: filters}
return
}
// Non default custom filter.
expRel, andRel := map[int]int{0: 0, 1: 2}, map[int]bool{0: true, 1: false}
for k, v := range tokens {
f.writeCustomFilter(fc, exp[expRel[k]], v)
if k == 1 {
fc.CustomFilters.And = andRel[exp[k]]
}
}
}
// writeCustomFilter provides a function to write the element.
func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string) {
operators := map[int]string{
1: "lessThan",
2: "equal",
3: "lessThanOrEqual",
4: "greaterThan",
5: "notEqual",
6: "greaterThanOrEqual",
22: "equal",
}
customFilter := xlsxCustomFilter{
Operator: operators[operator],
Val: val,
}
if fc.CustomFilters != nil {
fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
return
}
var customFilters []*xlsxCustomFilter
customFilters = append(customFilters, &customFilter)
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
}
// parseFilterExpression provides a function to converts the tokens of a
// possibly conditional expression into 1 or 2 sub expressions for further
// parsing.
//
// Examples:
//
// ('x', '==', 2000) -> exp1
// ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2
func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) {
var expressions []int
var t []string
if len(tokens) == 7 {
// The number of tokens will be either 3 (for 1 expression) or 7 (for 2
// expressions).
conditional, c := 0, tokens[3]
if conditionFormat.MatchString(c) {
conditional = 1
}
expression1, token1, err := f.parseFilterTokens(expression, tokens[:3])
if err != nil {
return expressions, t, err
}
expression2, token2, err := f.parseFilterTokens(expression, tokens[4:7])
if err != nil {
return expressions, t, err
}
return []int{expression1[0], conditional, expression2[0]}, []string{token1, token2}, nil
}
exp, token, err := f.parseFilterTokens(expression, tokens)
if err != nil {
return expressions, t, err
}
return exp, []string{token}, nil
}
// parseFilterTokens provides a function to parse the 3 tokens of a filter
// expression and return the operator and token.
func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, string, error) {
operators := map[string]int{
"==": 2,
"=": 2,
"=~": 2,
"eq": 2,
"!=": 5,
"!~": 5,
"ne": 5,
"<>": 5,
"<": 1,
"<=": 3,
">": 4,
">=": 6,
}
operator, ok := operators[strings.ToLower(tokens[1])]
if !ok {
// Convert the operator from a number to a descriptive string.
return []int{}, "", newUnknownFilterTokenError(tokens[1])
}
token := tokens[2]
// Special handling for Blanks/NonBlanks.
re := blankFormat.MatchString(strings.ToLower(token))
if re {
// Only allow Equals or NotEqual in this context.
if operator != 2 && operator != 5 {
return []int{operator}, token, newInvalidAutoFilterOperatorError(tokens[1], expression)
}
token = strings.ToLower(token)
// The operator should always be 2 (=) to flag a "simple" equality in
// the binary record. Therefore we convert <> to =.
if token == "blanks" {
if operator == 5 {
token = " "
}
} else {
if operator == 5 {
operator = 2
token = "blanks"
} else {
operator = 5
token = " "
}
}
}
// If the string token contains an Excel match character then change the
// operator type to indicate a non "simple" equality.
if re = matchFormat.MatchString(token); operator == 2 && re {
operator = 22
}
return []int{operator}, token, nil
}
================================================
FILE: table_test.go
================================================
package excelize
import (
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddTable(t *testing.T) {
f, err := prepareTestBook1()
assert.NoError(t, err)
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
assert.NoError(t, f.AddTable("Sheet2", &Table{
Range: "A2:B5",
Name: "table",
StyleName: "TableStyleMedium2",
ShowColumnStripes: true,
ShowFirstColumn: true,
ShowLastColumn: true,
ShowRowStripes: boolPtr(true),
}))
assert.NoError(t, f.AddTable("Sheet2", &Table{
Range: "D1:D11",
ShowHeaderRow: boolPtr(false),
}))
assert.NoError(t, f.AddTable("Sheet2", &Table{Range: "F1:F1", StyleName: "TableStyleMedium8"}))
// Test get tables in worksheet
tables, err := f.GetTables("Sheet2")
assert.Len(t, tables, 3)
assert.NoError(t, err)
// Test add table with already exist table name
assert.Equal(t, f.AddTable("Sheet2", &Table{Name: "Table1"}), ErrExistsTableName)
// Test add table with invalid table options
assert.Equal(t, f.AddTable("Sheet1", nil), ErrParameterInvalid)
// Test add table in not exist worksheet
assert.EqualError(t, f.AddTable("SheetN", &Table{Range: "B26:A21"}), "sheet SheetN does not exist")
// Test add table with illegal cell reference
assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))
// Test add table with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.AddTable("Sheet:1", &Table{Range: "B26:A21"}))
// Test addTable with illegal cell reference
f = NewFile()
assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil))
assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil))
// Test set defined name and add table with invalid name
for _, cases := range []struct {
name string
err error
}{
{name: "1Table", err: newInvalidNameError("1Table")},
{name: "-Table", err: newInvalidNameError("-Table")},
{name: "'Table", err: newInvalidNameError("'Table")},
{name: "Table 1", err: newInvalidNameError("Table 1")},
{name: "A&B", err: newInvalidNameError("A&B")},
{name: "_1Table'", err: newInvalidNameError("_1Table'")},
{name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidNameError("\u0f5f\u0fb3\u0f0b\u0f21")},
{name: strings.Repeat("c", MaxFieldLength+1), err: ErrNameLength},
} {
assert.Equal(t, cases.err, f.AddTable("Sheet1", &Table{
Range: "A1:B2",
Name: cases.name,
}))
assert.Equal(t, cases.err, f.SetDefinedName(&DefinedName{
Name: cases.name, RefersTo: "Sheet1!$A$2:$D$5",
}))
}
// Test check duplicate table name with unsupported charset table parts
f = NewFile()
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
assert.NoError(t, f.Close())
f = NewFile()
// Test add table with workbook with single cells parts
f.Pkg.Store("xl/tables/tableSingleCells1.xml", []byte(""))
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
// Test add table with workbook with unsupported charset single cells parts
f.Pkg.Store("xl/tables/tableSingleCells1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
assert.NoError(t, f.Close())
}
func TestGetTables(t *testing.T) {
f := NewFile()
// Test get tables in none table worksheet
tables, err := f.GetTables("Sheet1")
assert.Len(t, tables, 0)
assert.NoError(t, err)
// Test get tables in not exist worksheet
_, err = f.GetTables("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test adjust table with unsupported charset
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, err = f.GetTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test adjust table with no exist table parts
f.Pkg.Delete("xl/tables/table1.xml")
tables, err = f.GetTables("Sheet1")
assert.Len(t, tables, 0)
assert.NoError(t, err)
}
func TestDeleteTable(t *testing.T) {
f := NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"}))
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21", Name: "Table2"}))
assert.NoError(t, f.DeleteTable("Table2"))
assert.NoError(t, f.DeleteTable("Table1"))
// Test delete table with invalid table name
assert.Equal(t, newInvalidNameError("Table 1"), f.DeleteTable("Table 1"))
// Test delete table with no exist table name
assert.Equal(t, newNoExistTableError("Table"), f.DeleteTable("Table"))
// Test delete table with unsupported charset
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteTable("Table1"), "XML syntax error on line 1: invalid UTF-8")
// Test delete table without deleting table header
f = NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Date"))
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Values"))
assert.NoError(t, f.UpdateLinkedValue())
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2", Name: "Table1"}))
assert.NoError(t, f.DeleteTable("Table1"))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "Date", val)
val, err = f.GetCellValue("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "Values", val)
}
func TestSetTableColumns(t *testing.T) {
f := NewFile()
assert.Equal(t, newCoordinatesToCellNameError(1, 0), f.setTableColumns("Sheet1", true, 1, 0, 1, nil))
}
func TestAutoFilter(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
f, err := prepareTestBook1()
assert.NoError(t, err)
for i, opts := range [][]AutoFilterOptions{
{},
{{Column: "B", Expression: ""}},
{{Column: "B", Expression: "x != blanks"}},
{{Column: "B", Expression: "x == blanks"}},
{{Column: "B", Expression: "x != nonblanks"}},
{{Column: "B", Expression: "x == nonblanks"}},
{{Column: "B", Expression: "x <= 1 and x >= 2"}},
{{Column: "B", Expression: "x == 1 or x == 2"}},
{{Column: "B", Expression: "x == 1 or x == 2*"}},
} {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts))
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
})
}
// Test add auto filter with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.AutoFilter("Sheet:1", "A1:B1", nil))
// Test add auto filter with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AutoFilter("Sheet1", "A:B1", nil))
assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), f.AutoFilter("Sheet1", "A1:B", nil))
// Test add auto filter with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8")
// Test add auto filter with empty local sheet ID
f = NewFile()
f.WorkBook = &xlsxWorkbook{DefinedNames: &xlsxDefinedNames{DefinedName: []xlsxDefinedName{{Name: builtInDefinedNames[3], Hidden: true}}}}
assert.NoError(t, f.AutoFilter("Sheet1", "A1:B1", nil))
}
func TestAutoFilterError(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
f, err := prepareTestBook1()
assert.NoError(t, err)
for i, opts := range [][]AutoFilterOptions{
{{Column: "B", Expression: "x <= 1 and x >= blanks"}},
{{Column: "B", Expression: "x -- y or x == *2*"}},
{{Column: "B", Expression: "x != y or x ? *2"}},
{{Column: "B", Expression: "x -- y o r x == *2"}},
{{Column: "B", Expression: "x -- y"}},
{{Column: "A", Expression: "x -- y"}},
} {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) {
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
}
})
}
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.autoFilter("SheetN", "A1", 1, 1, []AutoFilterOptions{{
Column: "A",
Expression: "",
}}))
assert.Equal(t, newInvalidColumnNameError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
Column: "-",
Expression: "-",
}}))
assert.Equal(t, newInvalidAutoFilterColumnError("A"), f.autoFilter("Sheet1", "A1", 1, 100, []AutoFilterOptions{{
Column: "A",
Expression: "-",
}}))
assert.Equal(t, newInvalidAutoFilterExpError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
Column: "A",
Expression: "-",
}}))
}
func TestParseFilterTokens(t *testing.T) {
f := NewFile()
// Test with unknown operator
_, _, err := f.parseFilterTokens("", []string{"", "!"})
assert.EqualError(t, err, "unknown operator: !")
// Test invalid operator in context
_, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"})
assert.Equal(t, newInvalidAutoFilterOperatorError("<", ""), err)
}
================================================
FILE: templates.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
//
// This file contains default templates for XML files we don't yet populated
// based on content.
package excelize
import "encoding/xml"
// Source relationship and namespace list, associated prefixes and schema in which it was
// introduced.
var (
NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"}
NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"}
NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"}
NameSpaceDrawingMLA14 = xml.Attr{Name: xml.Name{Local: "a14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2010/main"}
NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"}
NameSpaceDrawingMLSlicer = xml.Attr{Name: xml.Name{Local: "sle", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2010/slicer"}
NameSpaceDrawingMLSlicerX15 = xml.Attr{Name: xml.Name{Local: "sle15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2012/slicer"}
NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"}
NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"}
NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
NameSpaceSpreadSheetXR10 = xml.Attr{Name: xml.Name{Local: "xr10", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"}
SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"}
SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"}
SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"}
SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"}
SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"}
)
// Source relationship and namespace.
const (
ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml"
ContentTypeCustomProperties = "application/vnd.openxmlformats-officedocument.custom-properties+xml"
ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml"
ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
ContentTypeRelationships = "application/vnd.openxmlformats-package.relationships+xml"
ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
ContentTypeSlicer = "application/vnd.ms-excel.slicer+xml"
ContentTypeSlicerCache = "application/vnd.ms-excel.slicerCache+xml"
ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml"
ContentTypeVBA = "application/vnd.ms-office.vbaProject"
ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing"
NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main"
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/"
NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/"
NameSpaceExtendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
NameSpaceXML = "http://www.w3.org/XML/1998/namespace"
NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance"
SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
SourceRelationshipCustomProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"
SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
SourceRelationshipExtendProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
SourceRelationshipSlicer = "http://schemas.microsoft.com/office/2007/relationships/slicer"
SourceRelationshipSlicerCache = "http://schemas.microsoft.com/office/2007/relationships/slicerCache"
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes"
StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main"
StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties"
StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main"
StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships"
StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
StrictSourceRelationshipExtendProperties = "http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties"
StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument"
// The following constants defined the extLst child element
// ([ISO/IEC29500-1:2016] section 18.2.10) of the workbook and worksheet
// elements extended by the addition of new child ext elements.
ExtURICalcFeatures = "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}"
ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"
ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"
EXtURICustomPropertyFmtID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"
ExtURIDataField = "{E15A36E0-9728-4E99-A89B-3F7291B0FE68}"
ExtURIDataModel = "{FCE2AD5D-F65C-4FA6-A056-5C36A1767C68}"
ExtURIDataValidations = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}"
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
ExtURIExternalLinkPr = "{FCE6A71B-6B00-49CD-AB44-F6B1AE7CDE65}"
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
ExtURIModelTimeGroupings = "{9835A34E-60A6-4A7C-AAB8-D5F71C897F49}"
ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}"
ExtURIPivotCachesX14 = "{876F7934-8845-4945-9796-88D515C7AA90}"
ExtURIPivotCachesX15 = "{841E416B-1EF1-43b6-AB56-02D37102CBD5}"
ExtURIPivotField = "{2946ED86-A175-432a-8AC1-64E0C546D7DE}"
ExtURIPivotFilter = "{0605FD5F-26C8-4aeb-8148-2DB25E43C511}"
ExtURIPivotHierarchy = "{F1805F06-0CD304483-9156-8803C3D141DF}"
ExtURIPivotTableReferences = "{983426D0-5260-488c-9760-48F4B6AC55F4}"
ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}"
ExtURISlicerCacheDefinition = "{2F2917AC-EB37-4324-AD4E-5DD8C200BD13}"
ExtURISlicerCacheHideItemsWithNoData = "{470722E0-AACD-4C17-9CDC-17EF765DBC7E}"
ExtURISlicerCachesX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}"
ExtURISlicerCachesX15 = "{46BE6895-7355-4a93-B00E-2C351335B9C9}"
ExtURISlicerListX14 = "{A8765BA9-456A-4dab-B4F3-ACF838C121DE}"
ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}"
ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
ExtURITimelineCachePivotCaches = "{A2CB5862-8E78-49c6-8D9D-AF26E26ADB89}"
ExtURITimelineCacheRefs = "{D0CA8CA8-9F24-4464-BF8E-62219DCF47F9}"
ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}"
ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}"
ExtURIWorkbookPrX14 = "{79F54976-1DA5-4618-B147-ACDE4B953A38}"
ExtURIWorkbookPrX15 = "{140A7094-0E35-4892-8432-C4D2E57EDEB5}"
)
// workbookExtURIPriority is the priority of URI in the workbook extension lists.
var workbookExtURIPriority = []string{
ExtURIPivotCachesX14,
ExtURISlicerCachesX14,
ExtURISlicerCachesX15,
ExtURIWorkbookPrX14,
ExtURIPivotCachesX15,
ExtURIPivotTableReferences,
ExtURITimelineCachePivotCaches,
ExtURITimelineCacheRefs,
ExtURIWorkbookPrX15,
ExtURIDataModel,
ExtURICalcFeatures,
ExtURIExternalLinkPr,
ExtURIModelTimeGroupings,
}
// worksheetExtURIPriority is the priority of URI in the worksheet extension lists.
var worksheetExtURIPriority = []string{
ExtURIConditionalFormattings,
ExtURIDataValidations,
ExtURISparklineGroups,
ExtURISlicerListX14,
ExtURIProtectedRanges,
ExtURIIgnoredErrors,
ExtURIWebExtensions,
ExtURISlicerListX15,
ExtURITimelineRefs,
ExtURIExternalLinkPr,
}
// Excel specifications and limits
const (
EMU = 9525
MaxCellStyles = 65430
MaxColumns = 16384
MaxColumnWidth = 255
MaxFieldLength = 255
MaxFilePathLength = 207
MaxFormControlValue = 30000
MaxFontFamilyLength = 31
MaxGraphicAltTextLength = 65535
MaxGraphicNameLength = 254
MaxFontSize = 409
MaxRowHeight = 409
MaxSheetNameLength = 31
MinColumns = 1
MinFontSize = 1
StreamChunkSize = 1 << 24
TotalCellChars = 32767
TotalRows = 1048576
TotalSheetHyperlinks = 65529
UnzipSizeLimit = 1000 << 24
// pivotTableVersion should be greater than 3. One or more of the
// PivotTables chosen are created in a version of Excel earlier than
// Excel 2007 or in compatibility mode. Slicer can only be used with
// PivotTables created in Excel 2007 or a newer version of Excel.
pivotTableVersion = 3
pivotTableRefreshedVersion = 8
defaultDrawingScale = 1.0
defaultChartDimensionWidth = 480
defaultChartDimensionHeight = 260
defaultSlicerWidth = 200
defaultSlicerHeight = 200
defaultChartLegendPosition = "bottom"
defaultChartShowBlanksAs = "gap"
defaultShapeSize = 160
defaultShapeLineWidth = 1
defaultColWidth = 9.140625
defaultColWidthPixels = 64.0
defaultRowHeight = 15.0
defaultRowHeightPixels = 20.0
defaultFontSize = 11.0
)
// ColorMappingType is the type of color transformation.
type ColorMappingType byte
// Color transformation types enumeration.
const (
ColorMappingTypeLight1 ColorMappingType = iota
ColorMappingTypeDark1
ColorMappingTypeLight2
ColorMappingTypeDark2
ColorMappingTypeAccent1
ColorMappingTypeAccent2
ColorMappingTypeAccent3
ColorMappingTypeAccent4
ColorMappingTypeAccent5
ColorMappingTypeAccent6
ColorMappingTypeHyperlink
ColorMappingTypeFollowedHyperlink
ColorMappingTypeUnset int = -1
)
// ChartDataLabelPositionType is the type of chart data labels position.
type ChartDataLabelPositionType byte
// Chart data labels positions types enumeration.
const (
ChartDataLabelsPositionUnset ChartDataLabelPositionType = iota
ChartDataLabelsPositionBestFit
ChartDataLabelsPositionBelow
ChartDataLabelsPositionCenter
ChartDataLabelsPositionInsideBase
ChartDataLabelsPositionInsideEnd
ChartDataLabelsPositionLeft
ChartDataLabelsPositionOutsideEnd
ChartDataLabelsPositionRight
ChartDataLabelsPositionAbove
)
// chartDataLabelsPositionTypes defined supported chart data labels position
// types.
var chartDataLabelsPositionTypes = map[ChartDataLabelPositionType]string{
ChartDataLabelsPositionBestFit: "bestFit",
ChartDataLabelsPositionBelow: "b",
ChartDataLabelsPositionCenter: "ctr",
ChartDataLabelsPositionInsideBase: "inBase",
ChartDataLabelsPositionInsideEnd: "inEnd",
ChartDataLabelsPositionLeft: "l",
ChartDataLabelsPositionOutsideEnd: "outEnd",
ChartDataLabelsPositionRight: "r",
ChartDataLabelsPositionAbove: "t",
}
// chartDashTypes defined supported preset chart dash types.
var chartDashTypes = map[ChartDashType]string{
ChartDashSolid: "solid",
ChartDashDot: "dot",
ChartDashDash: "dash",
ChartDashLgDash: "lgDash",
ChartDashSashDot: "dashDot",
ChartDashLgDashDot: "lgDashDot",
ChartDashLgDashDotDot: "lgDashDotDot",
ChartDashSysDash: "sysDash",
ChartDashSysDot: "sysDot",
ChartDashSysDashDot: "sysDashDot",
ChartDashSysDashDotDot: "sysDashDotDot",
}
// supportedChartDataLabelsPosition defined supported chart data labels position
// types for each type of chart.
var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionType{
Bar: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd},
BarStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd},
BarPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd},
Col: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd},
ColStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd},
ColPercentStacked: {ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideBase, ChartDataLabelsPositionInsideEnd},
Line: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove},
Pie: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd},
Pie3D: {ChartDataLabelsPositionBestFit, ChartDataLabelsPositionCenter, ChartDataLabelsPositionInsideEnd, ChartDataLabelsPositionOutsideEnd},
Scatter: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove},
Bubble: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove},
Bubble3D: {ChartDataLabelsPositionBelow, ChartDataLabelsPositionCenter, ChartDataLabelsPositionLeft, ChartDataLabelsPositionRight, ChartDataLabelsPositionAbove},
}
const (
defaultTempFileSST = "sharedStrings"
defaultXMLMetadata = "xl/metadata.xml"
defaultXMLPathCalcChain = "xl/calcChain.xml"
defaultXMLPathCellImages = "xl/cellimages.xml"
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
defaultXMLPathContentTypes = "[Content_Types].xml"
defaultXMLPathDocPropsApp = "docProps/app.xml"
defaultXMLPathDocPropsCore = "docProps/core.xml"
defaultXMLPathDocPropsCustom = "docProps/custom.xml"
defaultXMLPathRels = "_rels/.rels"
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
defaultXMLPathSheet = "xl/worksheets/sheet1.xml"
defaultXMLPathStyles = "xl/styles.xml"
defaultXMLPathTheme = "xl/theme/theme1.xml"
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
defaultXMLPathWorkbook = "xl/workbook.xml"
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
defaultXMLRdRichValue = "xl/richData/rdrichvalue.xml"
defaultXMLRdRichValueRel = "xl/richData/richValueRel.xml"
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
defaultXMLRdRichValueStructure = "xl/richData/rdrichvaluestructure.xml"
defaultXMLRdRichValueTypes = "xl/richData/rdRichValueTypes.xml"
defaultXMLRdRichValueWebImage = "xl/richData/rdRichValueWebImage.xml"
defaultXMLRdRichValueWebImageRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
)
// IndexedColorMapping is the table of default mappings from indexed color value
// to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards
// compatibility. A legacy indexing scheme for colors that is still required
// for some records, and for backwards compatibility with legacy formats. This
// element contains a sequence of RGB color values that correspond to color
// indexes (zero-based). When using the default indexed color palette, the
// values are not written out, but instead are implied. When the color palette
// has been modified from default, then the entire color palette is written
// out.
var IndexedColorMapping = []string{
"000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
"000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
"800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080",
"9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF",
"000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF",
"00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99",
"3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696",
"003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333",
"000000", "FFFFFF",
}
// supportedDefinedNameAtStartCharCodeRange list the valid first character of a
// defined name ASCII letters.
var supportedDefinedNameAtStartCharCodeRange = []int{
65, 90, 92, 92, 95, 95, 97, 122, 161, 161, 164, 164,
167, 168, 170, 170, 173, 173, 175, 186, 188, 696, 699, 705,
711, 711, 713, 715, 717, 717, 720, 721, 728, 731, 733, 733,
736, 740, 750, 750, 880, 883, 886, 887, 890, 893, 902, 902,
904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1315,
1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1610,
1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788,
1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026,
2036, 2037, 2042, 2042, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401,
2417, 2418, 2427, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480,
2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529,
2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611,
2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701,
2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749,
2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864,
2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929,
2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972,
2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084,
3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161,
3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257,
3261, 3261, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368,
3370, 3385, 3389, 3389, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505,
3507, 3515, 3517, 3517, 3520, 3526, 3585, 3642, 3648, 3662, 3713, 3714,
3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743,
3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763,
3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911,
3913, 3948, 3976, 3979, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189,
4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293,
4304, 4346, 4348, 4348, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4680,
4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749,
4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822,
4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740,
5743, 5750, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905,
5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103,
6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6400, 6428, 6480, 6509,
6512, 6516, 6528, 6569, 6593, 6599, 6656, 6678, 6917, 6963, 6981, 6987,
7043, 7072, 7086, 7087, 7168, 7203, 7245, 7247, 7258, 7293, 7424, 7615,
7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025,
8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126,
8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180,
8182, 8188, 8208, 8208, 8211, 8214, 8216, 8216, 8220, 8221, 8224, 8225,
8229, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8305, 8305,
8308, 8308, 8319, 8319, 8321, 8324, 8336, 8340, 8450, 8451, 8453, 8453,
8455, 8455, 8457, 8467, 8469, 8470, 8473, 8477, 8481, 8482, 8484, 8484,
8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521,
8526, 8526, 8531, 8532, 8539, 8542, 8544, 8584, 8592, 8601, 8658, 8658,
8660, 8660, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719,
8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741,
8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780,
8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835,
8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978,
9312, 9397, 9424, 9449, 9472, 9547, 9552, 9588, 9601, 9615, 9618, 9621,
9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665,
9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734,
9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794,
9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 11264, 11310,
11312, 11358, 11360, 11375, 11377, 11389, 11392, 11492, 11520, 11557, 11568, 11621,
11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710,
11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12288, 12291, 12293, 12311,
12317, 12319, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447,
12449, 12543, 12549, 12589, 12593, 12686, 12704, 12727, 12784, 12828, 12832, 12841,
12849, 12850, 12857, 12857, 12896, 12923, 12927, 12927, 12963, 12968, 13059, 13059,
13069, 13069, 13076, 13076, 13080, 13080, 13090, 13091, 13094, 13095, 13099, 13099,
13110, 13110, 13115, 13115, 13129, 13130, 13133, 13133, 13137, 13137, 13143, 13143,
13179, 13182, 13184, 13188, 13192, 13258, 13261, 13267, 13269, 13270, 13272, 13272,
13275, 13277, 13312, 19893, 19968, 40899, 40960, 42124, 42240, 42508, 42512, 42527,
42538, 42539, 42560, 42591, 42594, 42606, 42624, 42647, 42786, 42887, 42891, 42892,
43003, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187,
43274, 43301, 43312, 43334, 43520, 43560, 43584, 43586, 43588, 43595, 44032, 55203,
57344, 63560, 63744, 64045, 64048, 64106, 64112, 64217, 64256, 64262, 64275, 64279,
64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321,
64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019,
65072, 65073, 65075, 65092, 65097, 65106, 65108, 65111, 65113, 65126, 65128, 65131,
65136, 65140, 65142, 65276, 65281, 65374, 65377, 65470, 65474, 65479, 65482, 65487,
65490, 65495, 65498, 65500, 65504, 65510,
}
// supportedDefinedNameAfterStartCharCodeRange list the valid after first
// character of a defined name ASCII letters.
var supportedDefinedNameAfterStartCharCodeRange = []int{
46, 46, 48, 57, 63, 63, 65, 90, 92, 92, 95, 95,
97, 122, 161, 161, 164, 164, 167, 168, 170, 170, 173, 173,
175, 186, 188, 887, 890, 893, 900, 902, 904, 906, 908, 908,
910, 929, 931, 1315, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469,
1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522,
1536, 1539, 1542, 1544, 1547, 1547, 1550, 1562, 1567, 1567, 1569, 1630,
1632, 1641, 1646, 1747, 1749, 1791, 1807, 1866, 1869, 1969, 1984, 2038,
2042, 2042, 2305, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415,
2417, 2418, 2427, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472,
2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510,
2519, 2519, 2524, 2525, 2527, 2531, 2534, 2554, 2561, 2563, 2565, 2570,
2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617,
2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652,
2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728,
2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765,
2768, 2768, 2784, 2787, 2790, 2799, 2801, 2801, 2817, 2819, 2821, 2828,
2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884,
2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2929,
2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972,
2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016,
3018, 3021, 3024, 3024, 3031, 3031, 3046, 3066, 3073, 3075, 3077, 3084,
3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144,
3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3192, 3199,
3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257,
3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299,
3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368,
3370, 3385, 3389, 3396, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3427,
3430, 3445, 3449, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515,
3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551,
3570, 3571, 3585, 3642, 3647, 3662, 3664, 3673, 3713, 3714, 3716, 3716,
3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747,
3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780,
3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3843, 3859, 3897,
3902, 3911, 3913, 3948, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028,
4030, 4044, 4046, 4047, 4096, 4169, 4176, 4249, 4254, 4293, 4304, 4346,
4348, 4348, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4680, 4682, 4685,
4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784,
4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880,
4882, 4885, 4888, 4954, 4959, 4960, 4969, 4988, 4992, 5017, 5024, 5108,
5121, 5740, 5743, 5750, 5760, 5786, 5792, 5866, 5870, 5872, 5888, 5900,
5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003,
6016, 6099, 6103, 6103, 6107, 6109, 6112, 6121, 6128, 6137, 6155, 6158,
6160, 6169, 6176, 6263, 6272, 6314, 6400, 6428, 6432, 6443, 6448, 6459,
6464, 6464, 6470, 6509, 6512, 6516, 6528, 6569, 6576, 6601, 6608, 6617,
6624, 6683, 6912, 6987, 6992, 7001, 7009, 7036, 7040, 7082, 7086, 7097,
7168, 7223, 7232, 7241, 7245, 7293, 7424, 7654, 7678, 7957, 7960, 7965,
7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029,
8031, 8061, 8064, 8116, 8118, 8132, 8134, 8147, 8150, 8155, 8157, 8175,
8178, 8180, 8182, 8190, 8192, 8208, 8211, 8214, 8216, 8216, 8220, 8221,
8224, 8225, 8229, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8260, 8260,
8274, 8274, 8287, 8292, 8298, 8305, 8308, 8316, 8319, 8332, 8336, 8340,
8352, 8373, 8400, 8432, 8448, 8527, 8531, 8584, 8592, 9000, 9003, 9191,
9216, 9254, 9280, 9290, 9312, 9885, 9888, 9916, 9920, 9923, 9985, 9988,
9990, 9993, 9996, 10023, 10025, 10059, 10061, 10061, 10063, 10066, 10070, 10070,
10072, 10078, 10081, 10087, 10102, 10132, 10136, 10159, 10161, 10174, 10176, 10180,
10183, 10186, 10188, 10188, 10192, 10213, 10224, 10626, 10649, 10711, 10716, 10747,
10750, 11084, 11088, 11092, 11264, 11310, 11312, 11358, 11360, 11375, 11377, 11389,
11392, 11498, 11517, 11517, 11520, 11557, 11568, 11621, 11631, 11631, 11648, 11670,
11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726,
11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 11904, 11929, 11931, 12019,
12032, 12245, 12272, 12283, 12288, 12311, 12317, 12335, 12337, 12348, 12350, 12351,
12353, 12438, 12441, 12447, 12449, 12543, 12549, 12589, 12593, 12686, 12688, 12727,
12736, 12771, 12784, 12830, 12832, 12867, 12880, 13054, 13056, 19893, 19904, 40899,
40960, 42124, 42128, 42182, 42240, 42508, 42512, 42539, 42560, 42591, 42594, 42610,
42620, 42621, 42623, 42647, 42752, 42892, 43003, 43051, 43072, 43123, 43136, 43204,
43216, 43225, 43264, 43310, 43312, 43347, 43520, 43574, 43584, 43597, 43600, 43609,
44032, 55203, 55296, 64045, 64048, 64106, 64112, 64217, 64256, 64262, 64275, 64279,
64285, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433,
64467, 64829, 64848, 64911, 64914, 64967, 65008, 65021, 65024, 65039, 65056, 65062,
65072, 65073, 65075, 65092, 65097, 65106, 65108, 65111, 65113, 65126, 65128, 65131,
65136, 65140, 65142, 65276, 65279, 65279, 65281, 65374, 65377, 65470, 65474, 65479,
65482, 65487, 65490, 65495, 65498, 65500, 65504, 65510, 65512, 65518, 65529, 65533,
}
// supportedFontWidthFactors defines the average character width ratio of common
// font families. This is used for more accurate column width calculation when
// different font families are used.
var supportedFontWidthFactors = map[string][]float64{
"calibri": {0.97, 1.30, 1.00},
"agency fb": {0.69, 0.86, 1.00},
"aptos": {1.03, 1.37, 1.00},
"arial black": {1.35, 1.59, 1.00},
"arial narrow": {0.88, 1.20, 1.00},
"arial rounded mt bold": {1.07, 1.57, 1.00},
"arial unicode ms": {1.07, 1.42, 1.07},
"arial": {1.07, 1.42, 1.00},
"bahnschrift": {1.10, 1.30, 1.00},
"baskerville old face": {0.98, 1.56, 1.00},
"batang": {1.05, 1.31, 0.99},
"batangche": {1.10, 1.10, 1.10},
"bauhaus 93": {1.10, 1.43, 1.00},
"bell mt": {0.95, 1.33, 1.00},
"berlin sans fb": {1.03, 1.36, 1.00},
"bernard mt condensed": {0.94, 1.24, 1.00},
"blackadder itc": {0.76, 2.12, 1.00},
"bodoni mt condensed": {0.82, 1.17, 1.00},
"bodoni mt poster compressed": {0.72, 1.06, 1.00},
"bodoni mt": {1.06, 1.47, 1.00},
"book antiqua": {1.12, 1.44, 1.00},
"bookman old style": {1.14, 1.66, 1.00},
"broadway": {1.50, 1.75, 1.00},
"brush script mt": {0.82, 1.34, 1.00},
"calibri light": {0.98, 1.19, 1.00},
"calisto mt": {1.03, 1.42, 1.00},
"cambria math": {1.07, 1.28, 1.00},
"cambria": {1.07, 1.28, 1.00},
"candara light": {1.02, 1.35, 1.00},
"cascadia mono extralight": {1.42, 1.49, 1.00},
"castellar": {1.48, 1.58, 1.00},
"centaur": {0.93, 1.30, 1.00},
"century gothic": {1.19, 1.36, 1.00},
"century schoolbook": {1.10, 1.63, 1.00},
"century": {1.09, 1.52, 1.00},
"chiller": {0.90, 1.22, 1.00},
"colonna mt": {1.10, 1.40, 1.00},
"comic sans ms": {1.14, 1.46, 1.00},
"consolas": {1.21, 1.21, 1.00},
"cooper black": {1.28, 1.60, 1.00},
"copperplate gothic bold": {1.32, 1.62, 1.00},
"copperplate gothic light": {1.26, 1.58, 1.00},
"corbel light": {0.98, 1.26, 1.00},
"corbel": {1.03, 1.30, 1.00},
"courier new": {1.45, 1.45, 1.00},
"curlz mt": {1.16, 1.47, 1.00},
"david": {1.25, 1.28, 1.00},
"dengxian light": {1.02, 1.26, 1.02},
"dengxian": {1.05, 1.28, 1.08},
"dfkai-sb": {1.10, 1.10, 1.10},
"dotum": {1.25, 1.31, 0.99},
"dotumche": {1.10, 1.10, 1.10},
"dubai": {1.00, 1.32, 1.00},
"ebrima": {1.08, 1.31, 1.00},
"edwardian script itc": {0.82, 1.78, 1.00},
"elephant": {1.24, 1.86, 1.00},
"engravers mt": {1.99, 1.99, 1.00},
"eras bold itc": {1.14, 1.45, 1.00},
"eras demi itc": {1.10, 1.42, 1.00},
"eras light itc": {1.07, 1.40, 1.00},
"eras medium itc": {1.10, 1.42, 1.00},
"fangsong_gb2312": {1.20, 1.36, 1.10},
"felix titling": {1.20, 1.55, 1.00},
"footlight mt light": {1.03, 1.37, 1.00},
"forte": {0.93, 1.25, 1.00},
"franklin gothic demi cond": {0.88, 1.20, 1.00},
"franklin gothic demi": {1.08, 1.42, 1.00},
"franklin gothic heavy": {1.16, 1.45, 1.00},
"franklin gothic medium": {1.08, 1.42, 1.00},
"freestyle script": {0.87, 1.18, 1.00},
"french script mt": {0.82, 1.54, 1.00},
"fzshuti": {1.10, 1.36, 1.10},
"fzyaoti": {1.10, 1.36, 1.10},
"gabriola": {0.95, 1.28, 1.00},
"garamond": {1.16, 1.39, 1.00},
"georgia": {1.19, 1.43, 1.00},
"gigi": {0.95, 1.28, 1.00},
"gill sans mt condensed": {0.85, 1.18, 1.00},
"gill sans mt": {1.03, 1.36, 1.00},
"gill sans ultra bold": {1.30, 1.60, 1.00},
"gloucester mt extra condensed": {0.72, 1.02, 1.00},
"goudy old style": {0.98, 1.37, 1.00},
"goudy stout": {2.56, 2.80, 1.00},
"gulim": {1.19, 1.31, 0.99},
"gulimche": {1.20, 1.36, 1.10},
"gungsuh": {1.39, 1.39, 0.99},
"gungsuhche": {1.28, 1.36, 1.10},
"haettenschweiler": {0.82, 1.02, 1.00},
"harlow solid italic": {0.90, 1.82, 1.00},
"harrington": {1.07, 1.40, 1.00},
"helvetica neue": {1.10, 1.42, 1.00},
"helvetica": {1.07, 1.42, 1.00},
"high tower text": {0.97, 1.53, 1.00},
"impact": {1.01, 1.12, 1.00},
"informal roman": {1.07, 1.40, 1.00},
"ink free": {1.02, 1.33, 1.00},
"javanese text": {1.10, 1.42, 1.00},
"jokerman": {1.26, 1.67, 1.00},
"juice itc": {0.82, 1.14, 1.00},
"kaiti_gb2312": {1.20, 1.20, 1.10},
"kaiti": {1.20, 1.20, 1.10},
"kartika": {1.10, 1.32, 1.00},
"kokila": {1.17, 1.30, 1.00},
"kristen itc": {1.10, 1.42, 1.00},
"kunstler script": {0.72, 1.72, 1.00},
"latha": {1.17, 1.28, 1.00},
"leelawadee ui": {1.08, 1.31, 1.00},
"leelawadee": {1.03, 1.36, 1.00},
"lisu": {1.10, 1.42, 1.00},
"lucida bright": {1.18, 1.47, 1.00},
"lucida calligraphy": {1.18, 1.67, 1.00},
"lucida console": {1.32, 1.32, 1.00},
"lucida fax": {1.14, 1.45, 1.00},
"lucida handwriting": {1.39, 1.67, 1.00},
"lucida sans typewriter": {1.32, 1.32, 1.00},
"lucida sans unicode": {1.18, 1.47, 1.00},
"lucida sans": {1.18, 1.47, 1.00},
"lxgw wenkai": {1.10, 1.60, 1.10},
"magneto": {1.48, 1.98, 1.00},
"maiandra gd": {1.10, 1.42, 1.00},
"malgun gothic semilight": {1.07, 1.51, 1.07},
"malgun gothic": {1.10, 1.33, 1.10},
"mangal": {1.10, 1.42, 1.00},
"meiryo": {1.19, 1.42, 1.19},
"microsoft himalaya": {0.85, 0.96, 1.00},
"microsoft jhenghei light": {1.10, 1.30, 1.10},
"microsoft jhenghei ui": {1.16, 1.39, 1.16},
"microsoft jhenghei": {1.16, 1.39, 1.16},
"microsoft new tai lue": {1.03, 1.38, 1.00},
"microsoft phagspa": {1.00, 1.30, 1.00},
"microsoft sans serif": {1.07, 1.40, 1.00},
"microsoft tai le": {1.00, 1.30, 1.00},
"microsoft uighur": {0.90, 1.10, 1.00},
"microsoft yahei light": {1.10, 1.30, 1.10},
"microsoft yahei ui": {1.18, 1.30, 1.18},
"microsoft yahei": {1.18, 1.30, 1.18},
"microsoft yi baiti": {1.00, 1.12, 1.00},
"mingliu_hkscs": {1.10, 1.20, 1.10},
"mingliu-extb": {1.10, 1.20, 1.10},
"mingliu": {1.10, 1.20, 1.10},
"mistral": {0.87, 0.90, 1.00},
"modern no. 20": {0.90, 1.42, 1.00},
"mongolian baiti": {0.95, 1.38, 1.00},
"monotype corsiva": {0.85, 1.26, 1.00},
"ms gothic": {1.10, 1.10, 1.10},
"ms pgothic": {0.99, 1.31, 0.99},
"ms reference sans serif": {1.18, 1.47, 1.00},
"ms sans serif": {1.10, 1.42, 1.00},
"narkisim": {1.10, 1.25, 1.00},
"niagara engraved": {0.55, 0.58, 1.00},
"niagara solid": {0.48, 0.58, 1.00},
"nirmala ui semilight": {1.01, 1.32, 1.00},
"nirmala ui": {1.03, 1.36, 1.00},
"noto sans cjk": {1.10, 1.33, 1.10},
"noto serif cjk": {1.10, 1.33, 1.10},
"nsimsun": {1.10, 1.10, 1.10},
"nyala": {1.15, 1.32, 1.00},
"onyx": {0.68, 0.98, 1.00},
"palace script mt": {0.72, 1.22, 1.00},
"palatino linotype": {1.12, 1.44, 1.00},
"papyrus": {1.10, 1.82, 1.00},
"parchment": {0.74, 1.84, 1.00},
"perpetua titling mt": {1.23, 1.52, 1.00},
"perpetua": {0.95, 1.33, 1.00},
"playbill": {0.72, 1.02, 1.00},
"poor richard": {0.97, 1.36, 1.00},
"pristina": {0.87, 1.18, 1.00},
"rage italic": {0.82, 1.14, 1.00},
"ravie": {1.55, 1.86, 1.00},
"rockwell condensed": {0.90, 1.22, 1.00},
"rockwell extra bold": {1.42, 1.72, 1.00},
"rockwell": {1.16, 1.48, 1.00},
"rod": {1.25, 1.25, 1.00},
"sakkal majalla": {1.10, 1.22, 1.00},
"sarasa gothic": {1.10, 1.33, 1.10},
"segoe print": {1.16, 1.47, 1.00},
"segoe script": {1.10, 1.42, 1.00},
"segoe ui black": {1.12, 1.34, 1.00},
"segoe ui emoji": {1.08, 1.31, 1.00},
"segoe ui historic": {1.08, 1.31, 1.00},
"segoe ui semibold": {1.12, 1.34, 1.00},
"segoe ui semilight": {1.08, 1.31, 1.00},
"segoe ui symbol": {1.08, 1.31, 1.00},
"segoe ui": {1.08, 1.31, 1.00},
"shonar bangla": {1.00, 1.32, 1.00},
"showcard gothic": {1.50, 1.92, 1.00},
"shruti": {0.97, 1.30, 1.00},
"simfang": {1.10, 1.10, 1.10},
"simhei": {1.10, 1.10, 1.10},
"simkai": {1.10, 1.10, 1.10},
"simsun-extb": {1.10, 1.10, 1.10},
"simsun": {1.10, 1.10, 1.10},
"sitka banner": {0.95, 1.28, 1.00},
"sitka display": {1.00, 1.32, 1.00},
"sitka heading": {1.03, 1.36, 1.00},
"sitka small": {1.07, 1.40, 1.00},
"sitka subheading": {1.03, 1.36, 1.00},
"snap itc": {1.32, 1.62, 1.00},
"source han sans": {1.10, 1.33, 1.10},
"source han serif": {1.10, 1.33, 1.10},
"stcaiyun": {1.10, 1.10, 1.10},
"stencil": {1.16, 1.47, 1.00},
"stfangsong": {1.10, 1.90, 1.10},
"stheiti": {1.10, 1.10, 1.10},
"sthupo": {1.10, 1.90, 1.10},
"stkaiti": {1.10, 1.10, 1.10},
"stliti": {1.10, 1.10, 1.10},
"stsong": {1.10, 1.36, 1.10},
"stxingkai": {1.10, 1.36, 1.10},
"stxinwei": {1.10, 1.36, 1.10},
"stzhongsong": {1.10, 1.66, 1.10},
"sylfaen": {1.03, 1.36, 1.00},
"tahoma": {1.17, 1.30, 1.00},
"tempus sans itc": {1.14, 1.46, 1.00},
"times new roman": {1.11, 1.40, 1.00},
"traditional arabic": {0.95, 1.56, 1.00},
"trebuchet ms": {1.10, 1.26, 1.00},
"tunga": {1.15, 1.28, 1.00},
"tw cen mt condensed extra bold": {0.88, 1.22, 1.00},
"tw cen mt condensed": {0.86, 1.18, 1.00},
"tw cen mt": {0.98, 1.32, 1.00},
"ud digi kyokasho n-b": {1.30, 1.36, 1.10},
"ud digi kyokasho n-r": {1.30, 1.36, 1.10},
"ud digi kyokasho nk-b": {1.30, 1.36, 1.10},
"ud digi kyokasho nk-r": {1.30, 1.36, 1.10},
"ud デジタル 教科書体 n": {1.30, 1.36, 1.10},
"ud デジタル 教科書体 nk": {1.30, 1.36, 1.10},
"urdu typesetting": {1.52, 1.52, 1.00},
"utsaah": {1.43, 1.43, 1.00},
"vani": {1.27, 1.30, 1.00},
"verdana": {1.33, 1.44, 1.00},
"vijaya": {1.10, 1.22, 1.00},
"vivaldi": {0.76, 2.12, 1.00},
"vladimir script": {0.82, 1.54, 1.00},
"vrinda": {1.15, 1.28, 1.00},
"wide latin": {1.88, 2.65, 1.00},
"youyuan": {1.20, 1.36, 1.10},
"yu gothic light": {1.03, 1.34, 1.03},
"yu gothic medium": {1.12, 1.41, 1.12},
"yu gothic ui light": {1.03, 1.24, 1.03},
"yu gothic ui semibold": {1.12, 1.31, 1.12},
"yu gothic ui semilight": {1.03, 1.24, 1.03},
"yu gothic ui": {1.12, 1.41, 1.12},
"yu gothic": {1.12, 1.41, 1.12},
"yu mincho": {1.08, 1.48, 1.08},
"굴림": {0.99, 1.31, 1.01},
"굴림체": {1.10, 1.36, 1.20},
"궁서": {0.99, 1.31, 1.01},
"궁서체": {1.10, 1.36, 1.20},
"돋움": {0.99, 1.31, 0.99},
"돋움체": {1.10, 1.10, 1.15},
"맑은 고딕 semilight": {1.07, 1.51, 1.07},
"맑은 고딕": {1.10, 1.33, 1.10},
"바탕": {0.99, 1.31, 1.01},
"바탕체": {1.10, 1.10, 1.20},
"メイリオ": {1.19, 1.42, 1.19},
"仿宋": {1.20, 1.36, 1.40},
"华文中宋": {1.10, 1.66, 1.10},
"华文仿宋": {1.10, 1.90, 1.10},
"华文宋体": {1.10, 1.36, 1.10},
"华文彩云": {1.10, 1.10, 1.10},
"华文新魏": {1.10, 1.36, 1.10},
"华文楷体": {1.10, 1.10, 1.10},
"华文琥珀": {1.10, 1.90, 1.10},
"华文行楷": {1.10, 1.36, 1.10},
"华文隶书": {1.10, 1.10, 1.10},
"华文黑体": {1.10, 1.10, 1.10},
"宋体": {1.10, 1.10, 1.10},
"幼圆": {1.10, 1.36, 1.10},
"微软正黑体 light": {1.10, 1.30, 1.10},
"微软正黑体": {1.16, 1.39, 1.16},
"微软雅黑 light": {1.10, 1.30, 1.10},
"微软雅黑": {1.18, 1.30, 1.18},
"思源宋体": {1.10, 1.33, 1.10},
"思源黑体": {1.10, 1.33, 1.10},
"新宋体": {1.10, 1.10, 1.10},
"新细明体": {1.10, 1.20, 1.10},
"方正姚体": {1.10, 1.36, 1.10},
"方正舒体": {1.10, 1.36, 1.10},
"更纱黑体": {1.10, 1.33, 1.10},
"楷体": {1.20, 1.20, 1.10},
"標楷體": {1.20, 1.20, 1.10},
"游ゴシック light": {1.03, 1.34, 1.03},
"游ゴシック medium": {1.12, 1.41, 1.12},
"游ゴシック ui": {1.12, 1.41, 1.12},
"游ゴシック": {1.12, 1.41, 1.12},
"游明朝": {1.08, 1.48, 1.08},
"等线": {1.05, 1.28, 1.05},
"细明体": {1.10, 1.20, 1.10},
"隶书": {1.10, 1.10, 1.10},
"霞鹜文楷": {1.10, 1.60, 1.10},
"黑体": {1.10, 1.10, 1.10},
"\uff2d\uff33 \uff30ゴシック": {0.99, 1.31, 0.99},
"\uff2d\uff33 ゴシック": {1.10, 1.10, 1.10},
"\uff2d\uff33 明朝": {1.10, 1.10, 1.10},
}
// supportedImageTypes defined supported image types.
var supportedImageTypes = map[string]string{
".bmp": ".bmp", ".emf": ".emf", ".emz": ".emz", ".gif": ".gif",
".ico": ".ico", ".jpeg": ".jpeg", ".jpg": ".jpeg", ".png": ".png",
".svg": ".svg", ".tif": ".tiff", ".tiff": ".tiff", ".wmf": ".wmf",
".wmz": ".wmz",
}
// supportedCalcMode defined supported formula calculate mode.
var supportedCalcMode = []string{"manual", "auto", "autoNoTable"}
// supportedRefMode defined supported formula calculate mode.
var supportedRefMode = []string{"A1", "R1C1"}
// supportedContentTypes defined supported file format types.
var supportedContentTypes = map[string]string{
".xlam": ContentTypeAddinMacro,
".xlsm": ContentTypeMacro,
".xlsx": ContentTypeSheetML,
".xltm": ContentTypeTemplateMacro,
".xltx": ContentTypeTemplate,
}
// supportedUnderlineTypes defined supported underline types.
var supportedUnderlineTypes = []string{"none", "single", "double"}
// supportedVertAlignTypes defined supported vertical align types.
var supportedVertAlignTypes = []string{"baseline", "superscript", "subscript"}
// supportedDrawingUnderlineTypes defined supported underline types in drawing
// markup language.
var supportedDrawingUnderlineTypes = []string{
"none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy",
"wavyDbl",
}
// supportedDrawingTextVerticalType defined supported text vertical types in
// drawing markup language.
var supportedDrawingTextVerticalType = []string{"horz", "vert", "vert270", "wordArtVert", "eaVert", "mongolianVert", "wordArtVertRtl"}
// supportedPositioning defined supported positioning types.
var supportedPositioning = []string{"absolute", "oneCell", "twoCell"}
// supportedPageOrientation defined supported page setup page orientation.
var supportedPageOrientation = []string{"portrait", "landscape"}
// supportedPageOrder defined supported page setup page order.
var supportedPageOrder = []string{"overThenDown", "downThenOver"}
// builtInDefinedNames defined built-in defined names are built with a _xlnm prefix.
var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm.Criteria", "_xlnm._FilterDatabase", "_xlnm.Extract", "_xlnm.Consolidate_Area", "_xlnm.Database", "_xlnm.Sheet_Title"}
const templateDocpropsApp = `0Go Excelize`
const templateContentTypes = ``
const templateWorkbook = ``
const templateStyles = ``
const templateSheet = ``
const templateWorkbookRels = ``
const templateDocpropsCore = `xuri2006-09-16T00:00:00Z2006-09-16T00:00:00Z`
const templateRels = ``
const templateTheme = ``
const templateNamespaceIDMap = ` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:ap="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:op="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:cdr="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" xmlns:comp="http://schemas.openxmlformats.org/drawingml/2006/compatibility" xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xne="http://schemas.microsoft.com/office/excel/2006/main" xmlns:mso="http://schemas.microsoft.com/office/2006/01/customui" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:cppr="http://schemas.microsoft.com/office/2006/coverPageProps" xmlns:cdip="http://schemas.microsoft.com/office/2006/customDocumentInformationPanel" xmlns:ct="http://schemas.microsoft.com/office/2006/metadata/contentType" xmlns:ntns="http://schemas.microsoft.com/office/2006/metadata/customXsn" xmlns:lp="http://schemas.microsoft.com/office/2006/metadata/longProperties" xmlns:ma="http://schemas.microsoft.com/office/2006/metadata/properties/metaAttributes" xmlns:msink="http://schemas.microsoft.com/ink/2010/main" xmlns:c14="http://schemas.microsoft.com/office/drawing/2007/8/2/chart" xmlns:cdr14="http://schemas.microsoft.com/office/drawing/2010/chartDrawing" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" xmlns:pic14="http://schemas.microsoft.com/office/drawing/2010/picture" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:xdr14="http://schemas.microsoft.com/office/excel/2010/spreadsheetDrawing" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:dsp="http://schemas.microsoft.com/office/drawing/2008/diagram" xmlns:mso14="http://schemas.microsoft.com/office/2009/07/customui" xmlns:dgm14="http://schemas.microsoft.com/office/drawing/2010/diagram" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x12ac="http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr4="http://schemas.microsoft.com/office/spreadsheetml/2016/revision4" xmlns:xr5="http://schemas.microsoft.com/office/spreadsheetml/2016/revision5" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr7="http://schemas.microsoft.com/office/spreadsheetml/2016/revision7" xmlns:xr8="http://schemas.microsoft.com/office/spreadsheetml/2016/revision8" xmlns:xr9="http://schemas.microsoft.com/office/spreadsheetml/2016/revision9" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr11="http://schemas.microsoft.com/office/spreadsheetml/2016/revision11" xmlns:xr12="http://schemas.microsoft.com/office/spreadsheetml/2016/revision12" xmlns:xr13="http://schemas.microsoft.com/office/spreadsheetml/2016/revision13" xmlns:xr14="http://schemas.microsoft.com/office/spreadsheetml/2016/revision14" xmlns:xr15="http://schemas.microsoft.com/office/spreadsheetml/2016/revision15" xmlns:x16="http://schemas.microsoft.com/office/spreadsheetml/2014/11/main" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" mc:Ignorable="c14 cdr14 a14 pic14 x14 xdr14 x14ac dsp mso14 dgm14 x15 x12ac x15ac xr xr2 xr3 xr4 xr5 xr6 xr7 xr8 xr9 xr10 xr11 xr12 xr13 xr14 xr15 x15 x16 x16r2 mo mx mv o v" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xr:uid="{00000000-0001-0000-0000-000000000000}">`
================================================
FILE: vml.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"path/filepath"
"strconv"
"strings"
)
// FormControlType is the type of supported form controls.
type FormControlType byte
// This section defines the currently supported form control types enumeration.
const (
FormControlNote FormControlType = iota
FormControlButton
FormControlOptionButton
FormControlSpinButton
FormControlCheckBox
FormControlGroupBox
FormControlLabel
FormControlScrollBar
)
// HeaderFooterImagePositionType is the type of header and footer image position.
type HeaderFooterImagePositionType byte
// Worksheet header and footer image position types enumeration.
const (
HeaderFooterImagePositionLeft HeaderFooterImagePositionType = iota
HeaderFooterImagePositionCenter
HeaderFooterImagePositionRight
)
// GetComments retrieves all comments in a worksheet by given worksheet name.
func (f *File) GetComments(sheet string) ([]Comment, error) {
var comments []Comment
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
if !ok {
return comments, ErrSheetNotExist{sheet}
}
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
if !strings.HasPrefix(commentsXML, "/") {
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
}
commentsXML = strings.TrimPrefix(commentsXML, "/")
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return comments, err
}
if cmts != nil {
for _, cmt := range cmts.CommentList.Comment {
comment := Comment{}
if cmt.AuthorID < len(cmts.Authors.Author) {
comment.Author = cmts.Authors.Author[cmt.AuthorID]
}
comment.Cell = cmt.Ref
comment.AuthorID = cmt.AuthorID
if cmt.Text.T != nil {
comment.Text += *cmt.Text.T
}
for _, text := range cmt.Text.R {
if text.T != nil {
run := RichTextRun{Text: text.T.Val}
if text.RPr != nil {
run.Font = text.RPr.getFont()
}
comment.Paragraph = append(comment.Paragraph, run)
}
}
comments = append(comments, comment)
}
}
return comments, nil
}
// getSheetComments provides the method to get the target comment reference by
// given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string {
rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels")
if sheetRels := rels; sheetRels != nil {
sheetRels.mu.Lock()
defer sheetRels.mu.Unlock()
for _, v := range sheetRels.Relationships {
if v.Type == SourceRelationshipComments {
return v.Target
}
}
}
return ""
}
// AddComment provides the method to add comments in a sheet by giving the
// worksheet name, cell reference, and format set (such as author and text).
// Note that the maximum author name length is 255 and the max text length is
// 32512, and each cell can only have one comment, an error will return if
// adding comment on a cell which already exist comment. For example, add a
// rich-text comment with a specified comments box size in Sheet1!A5:
//
// err := f.AddComment("Sheet1", excelize.Comment{
// Cell: "A5",
// Author: "Excelize",
// Paragraph: []excelize.RichTextRun{
// {Text: "Excelize: ", Font: &excelize.Font{Bold: true}},
// {Text: "This is a comment."},
// },
// Height: 40,
// Width: 180,
// })
func (f *File) AddComment(sheet string, opts Comment) error {
return f.addVMLObject(vmlOptions{
sheet: sheet, Comment: opts,
FormControl: FormControl{
Cell: opts.Cell,
Type: FormControlNote,
Text: opts.Text,
Paragraph: opts.Paragraph,
Width: opts.Width,
Height: opts.Height,
},
})
}
// DeleteComment provides the method to delete comment in a worksheet by given
// worksheet name and cell reference. For example, delete the comment in
// Sheet1!$A$30:
//
// err := f.DeleteComment("Sheet1", "A30")
func (f *File) DeleteComment(sheet, cell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.LegacyDrawing == nil {
return err
}
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
if !strings.HasPrefix(commentsXML, "/") {
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
}
commentsXML = strings.TrimPrefix(commentsXML, "/")
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return err
}
if cmts != nil {
for i := 0; i < len(cmts.CommentList.Comment); i++ {
cmt := cmts.CommentList.Comment[i]
if cmt.Ref != cell {
continue
}
if len(cmts.CommentList.Comment) > 1 {
cmts.CommentList.Comment = append(
cmts.CommentList.Comment[:i],
cmts.CommentList.Comment[i+1:]...,
)
if idx := inStrSlice(cmts.cells, cell, true); idx != -1 {
cmts.cells = append(
cmts.cells[:idx],
cmts.cells[idx+1:]...,
)
}
i--
continue
}
cmts.CommentList.Comment = nil
cmts.cells = nil
}
f.Comments[commentsXML] = cmts
}
sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, true)
}
// deleteFormControl provides the method to delete shape from
// xl/drawings/vmlDrawing%d.xml by giving path, cell and shape type.
func (f *File) deleteFormControl(sheetRelationshipsDrawingVML, cell string, isComment bool) error {
col, row, err := CellNameToCoordinates(cell)
if err != nil {
return err
}
vmlID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML := strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
vml := f.VMLDrawing[drawingVML]
if vml == nil {
vml = &vmlDrawing{
XMLNSv: "urn:schemas-microsoft-com:vml",
XMLNSo: "urn:schemas-microsoft-com:office:office",
XMLNSx: "urn:schemas-microsoft-com:office:excel",
XMLNSmv: "http://macVmlSchemaUri",
ShapeLayout: &xlsxShapeLayout{
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: vmlID},
},
ShapeType: &xlsxShapeType{
Stroke: &xlsxStroke{JoinStyle: "miter"},
VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.Path = d.ShapeType.Path
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
Type: v.Type,
Style: v.Style,
Button: v.Button,
Filled: v.Filled,
FillColor: v.FillColor,
InsetMode: v.InsetMode,
Stroked: v.Stroked,
StrokeColor: v.StrokeColor,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
cond := func(objectType string) bool {
if isComment {
return objectType == "Note"
}
return objectType != "Note"
}
for i, sp := range vml.Shape {
var shapeVal decodeShapeVal
if err = xml.Unmarshal([]byte(fmt.Sprintf("%s", sp.Val)), &shapeVal); err == nil &&
cond(shapeVal.ClientData.ObjectType) && shapeVal.ClientData.Anchor != "" {
leftCol, topRow, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return err
}
if leftCol == col-1 && topRow == row-1 {
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
break
}
}
}
f.VMLDrawing[drawingVML] = vml
return err
}
// addComment provides a function to create chart as xl/comments%d.xml by
// given cell and format sets.
func (f *File) addComment(commentsXML string, opts vmlOptions) error {
if opts.Author == "" {
opts.Author = "Author"
}
if countUTF16String(opts.Author) > MaxFieldLength {
opts.Author = truncateUTF16Units(opts.Author, MaxFieldLength)
}
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return err
}
var authorID int
if cmts == nil {
cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{opts.Author}}}
}
if inStrSlice(cmts.cells, opts.Comment.Cell, true) != -1 {
return newAddCommentError(opts.Comment.Cell)
}
if inStrSlice(cmts.Authors.Author, opts.Author, true) == -1 {
cmts.Authors.Author = append(cmts.Authors.Author, opts.Author)
authorID = len(cmts.Authors.Author) - 1
}
defaultFont, err := f.GetDefaultFont()
if err != nil {
return err
}
chars, cmt := 0, xlsxComment{
Ref: opts.Comment.Cell,
AuthorID: authorID,
Text: xlsxText{R: []xlsxR{}},
}
if opts.Comment.Text != "" {
if countUTF16String(opts.Comment.Text) > TotalCellChars {
opts.Comment.Text = truncateUTF16Units(opts.Comment.Text, TotalCellChars)
}
cmt.Text.T = stringPtr(opts.Comment.Text)
chars += countUTF16String(opts.Comment.Text)
}
for _, run := range opts.Comment.Paragraph {
if chars == TotalCellChars {
break
}
if chars+countUTF16String(run.Text) > TotalCellChars {
run.Text = truncateUTF16Units(run.Text, TotalCellChars-chars)
}
chars += countUTF16String(run.Text)
r := xlsxR{
RPr: &xlsxRPr{
Sz: &attrValFloat{Val: float64Ptr(9)},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: stringPtr(defaultFont)},
Family: &attrValInt{Val: intPtr(2)},
},
T: &xlsxT{Val: run.Text, Space: xml.Attr{
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
Value: "preserve",
}},
}
if run.Font != nil {
r.RPr = run.Font.newRpr()
}
cmt.Text.R = append(cmt.Text.R, r)
}
cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt)
cmts.cells = append(cmts.cells, cmt.Ref)
f.Comments[commentsXML] = cmts
return err
}
// countComments provides a function to get comments files count storage in
// the folder xl.
func (f *File) countComments() int {
comments := map[string]struct{}{}
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/comments") {
comments[k.(string)] = struct{}{}
}
return true
})
for rel := range f.Comments {
if strings.Contains(rel, "xl/comments") {
comments[rel] = struct{}{}
}
}
return len(comments)
}
// commentsReader provides a function to get the pointer to the structure
// after deserialization of xl/comments%d.xml.
func (f *File) commentsReader(path string) (*xlsxComments, error) {
if f.Comments[path] == nil {
content, ok := f.Pkg.Load(path)
if ok && content != nil {
f.Comments[path] = new(xlsxComments)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(f.Comments[path]); err != nil && err != io.EOF {
return nil, err
}
}
}
if cmts := f.Comments[path]; cmts != nil && len(cmts.cells) == 0 {
for idx := 0; idx < len(cmts.CommentList.Comment); idx++ {
cmts.cells = append(cmts.cells, cmts.CommentList.Comment[idx].Ref)
}
f.Comments[path] = cmts
}
return f.Comments[path], nil
}
// commentsWriter provides a function to save xl/comments%d.xml after
// serialize structure.
func (f *File) commentsWriter() {
for path, c := range f.Comments {
if c != nil {
v, _ := xml.Marshal(c)
f.saveFileList(path, v)
}
}
}
// AddFormControl provides the method to add form control object in a worksheet
// by given worksheet name and form control options. Supported form control
// type: button, check box, group box, label, option button, scroll bar and
// spinner. If set macro for the form control, the workbook extension should be
// XLSM or XLTM. Scroll value must be between 0 and 30000. Please note that if a
// cell link is set for a checkbox form control, Excelize will not assign a
// value to the linked cell when the checkbox is checked. To reflect the
// checkbox state, please use the 'SetCellValue' function to manually set the
// linked cell's value to true.
//
// Example 1, add button form control with macro, rich-text, custom button size,
// print property on Sheet1!A2, and let the button do not move or size with
// cells:
//
// enable := true
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A2",
// Type: excelize.FormControlButton,
// Macro: "Button1_Click",
// Width: 140,
// Height: 60,
// Text: "Button 1\r\n",
// Paragraph: []excelize.RichTextRun{
// {
// Font: &excelize.Font{
// Bold: true,
// Italic: true,
// Underline: "single",
// Family: "Times New Roman",
// Size: 14,
// Color: "777777",
// },
// Text: "C1=A1+B1",
// },
// },
// Format: excelize.GraphicOptions{
// PrintObject: &enable,
// Positioning: "absolute",
// },
// })
//
// Example 2, add option button form control with checked status and text on
// Sheet1!A1:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A1",
// Type: excelize.FormControlOptionButton,
// Text: "Option Button 1",
// Checked: true,
// })
//
// Example 3, add spin button form control on Sheet1!B1 to increase or decrease
// the value of Sheet1!A1:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "B1",
// Type: excelize.FormControlSpinButton,
// Width: 15,
// Height: 40,
// CurrentVal: 7,
// MinVal: 5,
// MaxVal: 10,
// IncChange: 1,
// CellLink: "A1",
// })
//
// Example 4, add horizontally scroll bar form control on Sheet1!A2 to change
// the value of Sheet1!A1 by click the scroll arrows or drag the scroll box:
//
// err := f.AddFormControl("Sheet1", excelize.FormControl{
// Cell: "A2",
// Type: excelize.FormControlScrollBar,
// Width: 140,
// Height: 20,
// CurrentVal: 50,
// MinVal: 10,
// MaxVal: 100,
// IncChange: 1,
// PageChange: 1,
// CellLink: "A1",
// Horizontally: true,
// })
func (f *File) AddFormControl(sheet string, opts FormControl) error {
return f.addVMLObject(vmlOptions{
formCtrl: true, sheet: sheet, FormControl: opts,
})
}
// DeleteFormControl provides the method to delete form control in a worksheet
// by given worksheet name and cell reference. For example, delete the form
// control in Sheet1!$A$1:
//
// err := f.DeleteFormControl("Sheet1", "A1")
func (f *File) DeleteFormControl(sheet, cell string) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
if ws.LegacyDrawing == nil {
return err
}
sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
return f.deleteFormControl(sheetRelationshipsDrawingVML, cell, false)
}
// countVMLDrawing provides a function to get VML drawing files count storage
// in the folder xl/drawings.
func (f *File) countVMLDrawing() int {
drawings := map[string]struct{}{}
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/vmlDrawing") {
drawings[k.(string)] = struct{}{}
}
return true
})
for rel := range f.VMLDrawing {
if strings.Contains(rel, "xl/drawings/vmlDrawing") {
drawings[rel] = struct{}{}
}
}
return len(drawings)
}
// decodeVMLDrawingReader provides a function to get the pointer to the
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
if f.DecodeVMLDrawing[path] == nil {
c, ok := f.Pkg.Load(path)
if ok && c != nil {
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err := f.xmlNewDecoder(bytes.NewReader(bytesReplace(namespaceStrictToTransitional(c.([]byte)), []byte("
\r\n"), []byte("
\r\n"), -1))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
return nil, err
}
}
}
return f.DecodeVMLDrawing[path], nil
}
// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
// after serialize structure.
func (f *File) vmlDrawingWriter() {
for path, vml := range f.VMLDrawing {
if vml != nil {
v, _ := xml.Marshal(vml)
f.Pkg.Store(path, v)
}
}
}
// addVMLObject provides a function to create VML drawing parts and
// relationships for comments and form controls.
func (f *File) addVMLObject(opts vmlOptions) error {
// Read sheet data
ws, err := f.workSheetReader(opts.sheet)
if err != nil {
return err
}
vmlID := f.countComments() + 1
if opts.formCtrl {
if opts.Type > FormControlScrollBar {
return ErrParameterInvalid
}
vmlID = f.countVMLDrawing() + 1
}
sheetID := f.getSheetID(opts.sheet)
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetXMLPath, _ := f.getSheetXMLPath(opts.sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
if ws.LegacyDrawing != nil {
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.sheet, ws.LegacyDrawing.RID)
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
} else {
// Add first VML drawing for given sheet.
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetNameSpace(opts.sheet, SourceRelationship)
f.addSheetLegacyDrawing(opts.sheet, rID)
}
if err = f.addDrawingVML(sheetID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
return err
}
if !opts.formCtrl {
commentsXML := "xl/comments" + strconv.Itoa(vmlID) + ".xml"
if err = f.addComment(commentsXML, opts); err != nil {
return err
}
if sheetXMLPath, ok := f.getSheetXMLPath(opts.sheet); ok && f.getSheetComments(filepath.Base(sheetXMLPath)) == "" {
sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml"
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
}
}
return f.addContentTypePart(vmlID, "comments")
}
// prepareFormCtrlOptions provides a function to parse the format settings of
// the form control with default value.
func prepareFormCtrlOptions(opts *vmlOptions) *vmlOptions {
if opts.Format.ScaleX == 0 {
opts.Format.ScaleX = 1
}
if opts.Format.ScaleY == 0 {
opts.Format.ScaleY = 1
}
if opts.FormControl.Width == 0 {
opts.FormControl.Width = 140
}
if opts.FormControl.Height == 0 {
opts.FormControl.Height = 60
}
return opts
}
// formCtrlText returns font element in the VML for control form text.
func formCtrlText(opts *vmlOptions) []vmlFont {
var font []vmlFont
if opts.FormControl.Text != "" {
font = append(font, vmlFont{Content: opts.FormControl.Text})
}
for _, run := range opts.FormControl.Paragraph {
fnt := vmlFont{
Content: run.Text + "
\r\n",
}
if run.Font != nil {
fnt.Face = run.Font.Family
fnt.Color = run.Font.Color
if !strings.HasPrefix(run.Font.Color, "#") {
fnt.Color = "#" + fnt.Color
}
if run.Font.Size != 0 {
fnt.Size = uint(run.Font.Size * 20)
}
if run.Font.Underline == "single" {
fnt.Content = "" + fnt.Content + ""
}
if run.Font.Underline == "double" {
fnt.Content = "" + fnt.Content + ""
}
if run.Font.Italic {
fnt.Content = "" + fnt.Content + ""
}
if run.Font.Bold {
fnt.Content = "" + fnt.Content + ""
}
}
font = append(font, fnt)
}
return font
}
var formCtrlPresets = map[FormControlType]formCtrlPreset{
FormControlNote: {
objectType: "Note",
autoFill: "True",
filled: "",
fillColor: "#FBF6D6",
stroked: "",
strokeColor: "#EDEAA1",
strokeButton: "",
fill: &vFill{
Color2: "#FBFE82",
Angle: -180,
Type: "gradient",
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
},
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: &vShadow{On: "t", Color: "black", Obscured: "t"},
},
FormControlButton: {
objectType: "Button",
autoFill: "True",
filled: "",
fillColor: "buttonFace [67]",
stroked: "",
strokeColor: "windowText [64]",
strokeButton: "t",
fill: &vFill{
Color2: "buttonFace [67]",
Angle: -180,
Type: "gradient",
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
},
textHAlign: "Center",
textVAlign: "Center",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlCheckBox: {
objectType: "Checkbox",
autoFill: "True",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "Center",
noThreeD: stringPtr(""),
firstButton: nil,
shadow: nil,
},
FormControlGroupBox: {
objectType: "GBox",
autoFill: "False",
filled: "f",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: stringPtr(""),
firstButton: nil,
shadow: nil,
},
FormControlLabel: {
objectType: "Label",
autoFill: "False",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlOptionButton: {
objectType: "Radio",
autoFill: "False",
filled: "f",
fillColor: "window [65]",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "Center",
noThreeD: stringPtr(""),
firstButton: stringPtr(""),
shadow: nil,
},
FormControlScrollBar: {
objectType: "Scroll",
autoFill: "",
filled: "",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
FormControlSpinButton: {
objectType: "Spin",
autoFill: "False",
filled: "",
fillColor: "",
stroked: "f",
strokeColor: "windowText [64]",
strokeButton: "",
fill: nil,
textHAlign: "",
textVAlign: "",
noThreeD: nil,
firstButton: nil,
shadow: nil,
},
}
// addFormCtrl check and add scroll bar or spinner form control by given options.
func (sp *encodeShape) addFormCtrl(opts *vmlOptions) error {
if opts.Type != FormControlScrollBar && opts.Type != FormControlSpinButton {
return nil
}
if opts.CurrentVal > MaxFormControlValue ||
opts.MinVal > MaxFormControlValue ||
opts.MaxVal > MaxFormControlValue ||
opts.IncChange > MaxFormControlValue ||
opts.PageChange > MaxFormControlValue {
return ErrFormControlValue
}
if opts.CellLink != "" {
if _, _, err := CellNameToCoordinates(opts.CellLink); err != nil {
return err
}
}
sp.ClientData.FmlaLink = opts.CellLink
sp.ClientData.Val = opts.CurrentVal
sp.ClientData.Min = opts.MinVal
sp.ClientData.Max = opts.MaxVal
sp.ClientData.Inc = opts.IncChange
sp.ClientData.Page = opts.PageChange
if opts.Type == FormControlScrollBar {
if opts.Horizontally {
sp.ClientData.Horiz = stringPtr("")
}
sp.ClientData.Dx = 15
}
return nil
}
// addFormCtrlShape returns a VML shape by given preset and options.
func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor string, opts *vmlOptions) (*encodeShape, error) {
sp := encodeShape{
Fill: preset.fill,
Shadow: preset.shadow,
Path: &vPath{ConnectType: "none"},
TextBox: &vTextBox{
Style: "mso-direction-alt:auto",
Div: &xlsxDiv{Style: "text-align:left"},
},
ClientData: &xClientData{
ObjectType: preset.objectType,
Anchor: anchor,
AutoFill: preset.autoFill,
Row: intPtr(row - 1),
Column: intPtr(col - 1),
TextHAlign: preset.textHAlign,
TextVAlign: preset.textVAlign,
NoThreeD: preset.noThreeD,
FirstButton: preset.firstButton,
},
}
if opts.Format.PrintObject != nil && !*opts.Format.PrintObject {
sp.ClientData.PrintObject = "False"
}
if opts.Format.Positioning != "" {
idx := inStrSlice(supportedPositioning, opts.Format.Positioning, true)
if idx == -1 {
return &sp, newInvalidOptionalValue("Positioning", opts.Format.Positioning, supportedPositioning)
}
sp.ClientData.MoveWithCells = []*string{stringPtr(""), nil, nil}[idx]
sp.ClientData.SizeWithCells = []*string{stringPtr(""), stringPtr(""), nil}[idx]
}
if opts.Type == FormControlNote {
sp.ClientData.MoveWithCells = stringPtr("")
sp.ClientData.SizeWithCells = stringPtr("")
}
if !opts.formCtrl {
return &sp, nil
}
sp.TextBox.Div.Font = formCtrlText(opts)
sp.ClientData.FmlaMacro = opts.Macro
if (opts.Type == FormControlCheckBox || opts.Type == FormControlOptionButton) && opts.Checked {
sp.ClientData.Checked = 1
}
if opts.Type == FormControlCheckBox {
sp.ClientData.FmlaLink = opts.CellLink
}
return &sp, sp.addFormCtrl(opts)
}
// addDrawingVML provides a function to create VML drawing XML as
// xl/drawings/vmlDrawing%d.vml by given data ID, XML path and VML options. The
// anchor value is a comma-separated list of data written out as: LeftColumn,
// LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow,
// BottomOffset.
func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) error {
col, row, err := CellNameToCoordinates(opts.FormControl.Cell)
if err != nil {
return err
}
leftOffset, vmlID, vml, preset := 23, 202, f.VMLDrawing[drawingVML], formCtrlPresets[opts.Type]
style := "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden"
if opts.formCtrl {
leftOffset, vmlID = 0, 201
style = "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;mso-wrap-style:tight"
}
colStart, rowStart, colEnd, rowEnd, _, _, x2, y2 := f.positionObjectPixels(opts.sheet, col, row, int(opts.FormControl.Width), int(opts.FormControl.Height), &opts.Format)
anchor := fmt.Sprintf("%d, %d, %d, 0, %d, %d, %d, %d", colStart, leftOffset, rowStart, colEnd, x2, rowEnd, y2)
if vml == nil {
vml = &vmlDrawing{
XMLNSv: "urn:schemas-microsoft-com:vml",
XMLNSo: "urn:schemas-microsoft-com:office:office",
XMLNSx: "urn:schemas-microsoft-com:office:excel",
XMLNSmv: "http://macVmlSchemaUri",
ShapeLayout: &xlsxShapeLayout{
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
},
ShapeType: &xlsxShapeType{
ID: fmt.Sprintf("_x0000_t%d", vmlID),
CoordSize: "21600,21600",
Spt: 202,
Path: "m0,0l0,21600,21600,21600,21600,0xe",
Stroke: &xlsxStroke{JoinStyle: "miter"},
VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.Path = d.ShapeType.Path
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
Type: v.Type,
Style: v.Style,
Button: v.Button,
Filled: v.Filled,
FillColor: v.FillColor,
InsetMode: v.InsetMode,
Stroked: v.Stroked,
StrokeColor: v.StrokeColor,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
sp, err := f.addFormCtrlShape(preset, col, row, anchor, opts)
if err != nil {
return err
}
s, _ := xml.Marshal(sp)
shape := xlsxShape{
ID: "_x0000_s1025",
Type: fmt.Sprintf("#_x0000_t%d", vmlID),
Style: style,
Button: preset.strokeButton,
Filled: preset.filled,
FillColor: preset.fillColor,
Stroked: preset.stroked,
StrokeColor: preset.strokeColor,
Val: string(s[13 : len(s)-14]),
}
vml.Shape = append(vml.Shape, shape)
f.VMLDrawing[drawingVML] = vml
return err
}
// GetFormControls retrieves all form controls in a worksheet by a given
// worksheet name. Note that, this function does not support getting the width
// and height of the form controls currently.
func (f *File) GetFormControls(sheet string) ([]FormControl, error) {
var formControls []FormControl
// Read sheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return formControls, err
}
if ws.LegacyDrawing == nil {
return formControls, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
drawingVML := strings.ReplaceAll(target, "..", "xl")
vml := f.VMLDrawing[drawingVML]
if vml == nil {
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return formControls, err
}
for _, sp := range d.Shape {
if sp.Type != "#_x0000_t201" {
continue
}
formControl, err := extractFormControl(sp.Val)
if err != nil {
return formControls, err
}
if formControl.Type == FormControlNote || formControl.Cell == "" {
continue
}
formControls = append(formControls, formControl)
}
return formControls, err
}
for _, sp := range vml.Shape {
if sp.Type != "#_x0000_t201" {
continue
}
formControl, err := extractFormControl(sp.Val)
if err != nil {
return formControls, err
}
if formControl.Type == FormControlNote || formControl.Cell == "" {
continue
}
formControls = append(formControls, formControl)
}
return formControls, err
}
// extractFormControl provides a function to extract form controls for a
// worksheets by given client data.
func extractFormControl(clientData string) (FormControl, error) {
var (
err error
formControl FormControl
shapeVal decodeShapeVal
)
if err = xml.Unmarshal([]byte(fmt.Sprintf("%s", clientData)), &shapeVal); err != nil {
return formControl, err
}
for formCtrlType, preset := range formCtrlPresets {
if shapeVal.ClientData.ObjectType == preset.objectType && shapeVal.ClientData.Anchor != "" {
formControl.Paragraph = extractVMLFont(shapeVal.TextBox.Div.Font)
if len(formControl.Paragraph) > 0 && formControl.Paragraph[0].Font == nil {
formControl.Text = formControl.Paragraph[0].Text
formControl.Paragraph = formControl.Paragraph[1:]
}
formControl.Type = formCtrlType
col, row, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return formControl, err
}
if formControl.Cell, err = CoordinatesToCellName(col+1, row+1); err != nil {
return formControl, err
}
formControl.Macro = shapeVal.ClientData.FmlaMacro
formControl.Checked = shapeVal.ClientData.Checked != 0
formControl.CellLink = shapeVal.ClientData.FmlaLink
formControl.CurrentVal = shapeVal.ClientData.Val
formControl.MinVal = shapeVal.ClientData.Min
formControl.MaxVal = shapeVal.ClientData.Max
formControl.IncChange = shapeVal.ClientData.Inc
formControl.PageChange = shapeVal.ClientData.Page
formControl.Horizontally = shapeVal.ClientData.Horiz != nil
}
}
return formControl, err
}
// extractAnchorCell extract left-top cell coordinates from given VML anchor
// comma-separated list values.
func extractAnchorCell(anchor string) (int, int, error) {
var (
leftCol, topRow int
err error
pos = strings.Split(anchor, ",")
)
if len(pos) != 8 {
return leftCol, topRow, ErrParameterInvalid
}
leftCol, err = strconv.Atoi(strings.TrimSpace(pos[0]))
if err != nil {
return leftCol, topRow, ErrColumnNumber
}
topRow, err = strconv.Atoi(strings.TrimSpace(pos[2]))
return leftCol, topRow, err
}
// extractVMLFont extract rich-text and font format from given VML font element.
func extractVMLFont(font []decodeVMLFont) []RichTextRun {
var runs []RichTextRun
extractU := func(u *decodeVMLFontU, run *RichTextRun) {
if u == nil {
return
}
run.Text += u.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Underline = "single"
if u.Class == "font1" {
run.Font.Underline = "double"
}
}
extractI := func(i *decodeVMLFontI, run *RichTextRun) {
if i == nil {
return
}
extractU(i.U, run)
run.Text += i.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Italic = true
}
extractB := func(b *decodeVMLFontB, run *RichTextRun) {
if b == nil {
return
}
extractI(b.I, run)
run.Text += b.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Bold = true
}
for _, fnt := range font {
var run RichTextRun
extractB(fnt.B, &run)
extractI(fnt.I, &run)
extractU(fnt.U, &run)
run.Text += fnt.Val
if fnt.Face != "" || fnt.Size > 0 || fnt.Color != "" {
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Family = fnt.Face
run.Font.Size = float64(fnt.Size / 20)
run.Font.Color = fnt.Color
}
runs = append(runs, run)
}
return runs
}
// AddHeaderFooterImage provides a mechanism to set the graphics that can be
// referenced in the header and footer definitions via &G, supported image
// types: EMF, EMZ, GIF, ICO, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
//
// The extension should be provided with a "." in front, e.g. ".png".
// The width and height should have units in them, e.g. "100pt".
func (f *File) AddHeaderFooterImage(sheet string, opts *HeaderFooterImageOptions) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ext, ok := supportedImageTypes[strings.ToLower(opts.Extension)]
if !ok {
return ErrImgExt
}
sheetID := f.getSheetID(sheet)
vmlID := f.countVMLDrawing() + 1
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
if ws.LegacyDrawingHF != nil {
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawingHF.RID)
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
} else {
// Add first VML drawing for given sheet.
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetNameSpace(sheet, SourceRelationship)
f.addSheetLegacyDrawingHF(sheet, rID)
}
shapeID := map[HeaderFooterImagePositionType]string{
HeaderFooterImagePositionLeft: "L",
HeaderFooterImagePositionCenter: "C",
HeaderFooterImagePositionRight: "R",
}[opts.Position] +
map[bool]string{false: "H", true: "F"}[opts.IsFooter] +
map[bool]string{false: "", true: "FIRST"}[opts.FirstPage]
vml := f.VMLDrawing[drawingVML]
if vml == nil {
vml = &vmlDrawing{
XMLNSv: "urn:schemas-microsoft-com:vml",
XMLNSo: "urn:schemas-microsoft-com:office:office",
XMLNSx: "urn:schemas-microsoft-com:office:excel",
ShapeLayout: &xlsxShapeLayout{
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
},
ShapeType: &xlsxShapeType{
ID: "_x0000_t75",
CoordSize: "21600,21600",
Spt: 75,
PreferRelative: "t",
Path: "m@4@5l@4@11@9@11@9@5xe",
Filled: "f",
Stroked: "f",
Stroke: &xlsxStroke{JoinStyle: "miter"},
VFormulas: &vFormulas{
Formulas: []vFormula{
{Equation: "if lineDrawn pixelLineWidth 0"},
{Equation: "sum @0 1 0"},
{Equation: "sum 0 0 @1"},
{Equation: "prod @2 1 2"},
{Equation: "prod @3 21600 pixelWidth"},
{Equation: "prod @3 21600 pixelHeight"},
{Equation: "sum @0 0 1"},
{Equation: "prod @6 1 2"},
{Equation: "prod @7 21600 pixelWidth"},
{Equation: "sum @8 21600 0"},
{Equation: "prod @7 21600 pixelHeight"},
{Equation: "sum @10 21600 0"},
},
},
VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"},
Lock: &oLock{Ext: "edit", AspectRatio: "t"},
},
}
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
vml.ShapeType.ID = d.ShapeType.ID
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
vml.ShapeType.Spt = d.ShapeType.Spt
vml.ShapeType.PreferRelative = d.ShapeType.PreferRelative
vml.ShapeType.Path = d.ShapeType.Path
vml.ShapeType.Filled = d.ShapeType.Filled
vml.ShapeType.Stroked = d.ShapeType.Stroked
for _, v := range d.Shape {
s := xlsxShape{
ID: v.ID,
SpID: v.SpID,
Type: v.Type,
Style: v.Style,
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
}
for idx, shape := range vml.Shape {
if shape.ID == shapeID {
vml.Shape = append(vml.Shape[:idx], vml.Shape[idx+1:]...)
}
}
style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", opts.Width, opts.Height)
drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels"
mediaStr := ".." + strings.TrimPrefix(f.addMedia(opts.File, ext), "xl")
imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "")
shape := xlsxShape{
ID: shapeID,
SpID: "_x0000_s1025",
Type: "#_x0000_t75",
Style: style,
}
sp, _ := xml.Marshal(encodeShape{
ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)},
Lock: &oLock{Ext: "edit", Rotation: "t"},
})
shape.Val = string(sp[13 : len(sp)-14])
vml.Shape = append(vml.Shape, shape)
f.VMLDrawing[drawingVML] = vml
if err := f.setContentTypePartImageExtensions(); err != nil {
return err
}
return f.setContentTypePartVMLExtensions()
}
================================================
FILE: vmlDrawing.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// vmlDrawing directly maps the root element in the file
// xl/drawings/vmlDrawing%d.vml.
type vmlDrawing struct {
XMLName xml.Name `xml:"xml"`
XMLNSv string `xml:"xmlns:v,attr"`
XMLNSo string `xml:"xmlns:o,attr"`
XMLNSx string `xml:"xmlns:x,attr"`
XMLNSmv string `xml:"xmlns:mv,attr,omitempty"`
ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"`
ShapeType *xlsxShapeType `xml:"v:shapetype"`
Shape []xlsxShape `xml:"v:shape"`
}
// xlsxShapeLayout directly maps the shapelayout element. This element contains
// child elements that store information used in the editing and layout of
// shapes.
type xlsxShapeLayout struct {
Ext string `xml:"v:ext,attr"`
IDmap *xlsxIDmap `xml:"o:idmap"`
}
// xlsxIDmap directly maps the idmap element.
type xlsxIDmap struct {
Ext string `xml:"v:ext,attr"`
Data int `xml:"data,attr"`
}
// xlsxShape directly maps the shape element.
type xlsxShape struct {
XMLName xml.Name `xml:"v:shape"`
ID string `xml:"id,attr"`
SpID string `xml:"o:spid,attr,omitempty"`
Type string `xml:"type,attr"`
Style string `xml:"style,attr"`
Button string `xml:"o:button,attr,omitempty"`
Filled string `xml:"filled,attr,omitempty"`
FillColor string `xml:"fillcolor,attr,omitempty"`
InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
StrokeColor string `xml:"strokecolor,attr,omitempty"`
Val string `xml:",innerxml"`
}
// xlsxShapeType directly maps the shapetype element.
type xlsxShapeType struct {
ID string `xml:"id,attr"`
CoordSize string `xml:"coordsize,attr"`
Spt int `xml:"o:spt,attr"`
PreferRelative string `xml:"o:preferrelative,attr,omitempty"`
Path string `xml:"path,attr"`
Filled string `xml:"filled,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
Stroke *xlsxStroke `xml:"v:stroke"`
VFormulas *vFormulas `xml:"v:formulas"`
VPath *vPath `xml:"v:path"`
Lock *oLock `xml:"o:lock"`
}
// xlsxStroke directly maps the stroke element.
type xlsxStroke struct {
JoinStyle string `xml:"joinstyle,attr"`
}
// vPath directly maps the v:path element.
type vPath struct {
ExtrusionOK string `xml:"o:extrusionok,attr,omitempty"`
GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"`
ConnectType string `xml:"o:connecttype,attr"`
}
// oLock directly maps the o:lock element.
type oLock struct {
Ext string `xml:"v:ext,attr"`
Rotation string `xml:"rotation,attr,omitempty"`
AspectRatio string `xml:"aspectratio,attr,omitempty"`
}
// vFormulas directly maps to the v:formulas element
type vFormulas struct {
Formulas []vFormula `xml:"v:f"`
}
// vFormula directly maps to the v:f element
type vFormula struct {
Equation string `xml:"eqn,attr"`
}
// vFill directly maps the v:fill element. This element must be defined within a
// Shape element.
type vFill struct {
Angle int `xml:"angle,attr,omitempty"`
Color2 string `xml:"color2,attr"`
Type string `xml:"type,attr,omitempty"`
Fill *oFill `xml:"o:fill"`
}
// oFill directly maps the o:fill element.
type oFill struct {
Ext string `xml:"v:ext,attr"`
Type string `xml:"type,attr,omitempty"`
}
// vShadow directly maps the v:shadow element. This element must be defined
// within a Shape element. In addition, the On attribute must be set to True.
type vShadow struct {
On string `xml:"on,attr"`
Color string `xml:"color,attr,omitempty"`
Obscured string `xml:"obscured,attr"`
}
// vTextBox directly maps the v:textbox element. This element must be defined
// within a Shape element.
type vTextBox struct {
Style string `xml:"style,attr"`
Div *xlsxDiv `xml:"div"`
}
// vImageData directly maps the v:imagedata element. This element must be
// defined within a Shape element.
type vImageData struct {
RelID string `xml:"o:relid,attr"`
Title string `xml:"o:title,attr,omitempty"`
}
// xlsxDiv directly maps the div element.
type xlsxDiv struct {
Style string `xml:"style,attr"`
Font []vmlFont `xml:"font"`
}
type vmlFont struct {
Face string `xml:"face,attr,omitempty"`
Size uint `xml:"size,attr,omitempty"`
Color string `xml:"color,attr,omitempty"`
Content string `xml:",innerxml"`
}
// xClientData (Attached Object Data) directly maps the x:ClientData element.
// This element specifies data associated with objects attached to a
// spreadsheet. While this element might contain any of the child elements
// below, only certain combinations are meaningful. The ObjectType attribute
// determines the kind of object the element represents and which subset of
// child elements is appropriate. Relevant groups are identified for each child
// element.
type xClientData struct {
ObjectType string `xml:"ObjectType,attr"`
MoveWithCells *string `xml:"x:MoveWithCells"`
SizeWithCells *string `xml:"x:SizeWithCells"`
Anchor string `xml:"x:Anchor"`
Locked string `xml:"x:Locked,omitempty"`
PrintObject string `xml:"x:PrintObject,omitempty"`
AutoFill string `xml:"x:AutoFill,omitempty"`
FmlaMacro string `xml:"x:FmlaMacro,omitempty"`
TextHAlign string `xml:"x:TextHAlign,omitempty"`
TextVAlign string `xml:"x:TextVAlign,omitempty"`
Row *int `xml:"x:Row"`
Column *int `xml:"x:Column"`
Checked int `xml:"x:Checked,omitempty"`
FmlaLink string `xml:"x:FmlaLink,omitempty"`
NoThreeD *string `xml:"x:NoThreeD"`
FirstButton *string `xml:"x:FirstButton"`
Val uint `xml:"x:Val,omitempty"`
Min uint `xml:"x:Min,omitempty"`
Max uint `xml:"x:Max,omitempty"`
Inc uint `xml:"x:Inc,omitempty"`
Page uint `xml:"x:Page,omitempty"`
Horiz *string `xml:"x:Horiz"`
Dx uint `xml:"x:Dx,omitempty"`
}
// decodeVmlDrawing defines the structure used to parse the file
// xl/drawings/vmlDrawing%d.vml.
type decodeVmlDrawing struct {
ShapeType decodeShapeType `xml:"urn:schemas-microsoft-com:vml shapetype"`
Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"`
}
// decodeShapeType defines the structure used to parse the shapetype element in
// the file xl/drawings/vmlDrawing%d.vml.
type decodeShapeType struct {
ID string `xml:"id,attr"`
CoordSize string `xml:"coordsize,attr"`
Spt int `xml:"spt,attr"`
PreferRelative string `xml:"preferrelative,attr,omitempty"`
Path string `xml:"path,attr"`
Filled string `xml:"filled,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
}
// decodeShape defines the structure used to parse the particular shape element.
type decodeShape struct {
ID string `xml:"id,attr"`
SpID string `xml:"spid,attr,omitempty"`
Type string `xml:"type,attr"`
Style string `xml:"style,attr"`
Button string `xml:"button,attr,omitempty"`
Filled string `xml:"filled,attr,omitempty"`
FillColor string `xml:"fillcolor,attr,omitempty"`
InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
StrokeColor string `xml:"strokecolor,attr,omitempty"`
Val string `xml:",innerxml"`
}
// decodeShapeVal defines the structure used to parse the sub-element of the
// shape in the file xl/drawings/vmlDrawing%d.vml.
type decodeShapeVal struct {
TextBox decodeVMLTextBox `xml:"textbox"`
ClientData decodeVMLClientData `xml:"ClientData"`
}
// decodeVMLFontU defines the structure used to parse the u element in the VML.
type decodeVMLFontU struct {
Class string `xml:"class,attr"`
Val string `xml:",chardata"`
}
// decodeVMLFontI defines the structure used to parse the i element in the VML.
type decodeVMLFontI struct {
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}
// decodeVMLFontB defines the structure used to parse the b element in the VML.
type decodeVMLFontB struct {
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}
// decodeVMLFont defines the structure used to parse the font element in the VML.
type decodeVMLFont struct {
Face string `xml:"face,attr,omitempty"`
Size uint `xml:"size,attr,omitempty"`
Color string `xml:"color,attr,omitempty"`
B *decodeVMLFontB `xml:"b"`
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}
// decodeVMLDiv defines the structure used to parse the div element in the VML.
type decodeVMLDiv struct {
Font []decodeVMLFont `xml:"font"`
}
// decodeVMLTextBox defines the structure used to parse the v:textbox element in
// the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLTextBox struct {
Div decodeVMLDiv `xml:"div"`
}
// decodeVMLClientData defines the structure used to parse the x:ClientData
// element in the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLClientData struct {
ObjectType string `xml:"ObjectType,attr"`
Anchor string
FmlaMacro string
Column *int
Row *int
Checked int
FmlaLink string
Val uint
Min uint
Max uint
Inc uint
Page uint
Horiz *string
}
// encodeShape defines the structure used to re-serialization shape element.
type encodeShape struct {
Fill *vFill `xml:"v:fill"`
Shadow *vShadow `xml:"v:shadow"`
Path *vPath `xml:"v:path"`
TextBox *vTextBox `xml:"v:textbox"`
ImageData *vImageData `xml:"v:imagedata"`
ClientData *xClientData `xml:"x:ClientData"`
Lock *oLock `xml:"o:lock"`
}
// formCtrlPreset defines the structure used to form control presets.
type formCtrlPreset struct {
autoFill string
fill *vFill
fillColor string
filled string
firstButton *string
noThreeD *string
objectType string
shadow *vShadow
strokeButton string
strokeColor string
stroked string
textHAlign string
textVAlign string
}
// vmlOptions defines the structure used to internal comments and form controls.
type vmlOptions struct {
formCtrl bool
sheet string
Comment
FormControl
}
// FormControl directly maps the form controls information.
type FormControl struct {
Cell string
Macro string
Width uint
Height uint
Checked bool
CurrentVal uint
MinVal uint
MaxVal uint
IncChange uint
PageChange uint
Horizontally bool
CellLink string
Text string
Paragraph []RichTextRun
Type FormControlType
Format GraphicOptions
}
// HeaderFooterImageOptions defines the settings for an image to be accessible
// from the worksheet header and footer options.
type HeaderFooterImageOptions struct {
Position HeaderFooterImagePositionType
File []byte
IsFooter bool
FirstPage bool
Extension string
Width string
Height string
}
================================================
FILE: vml_test.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddComment(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
s := strings.Repeat("c", TotalCellChars+1)
assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A30", Author: s, Text: s, Paragraph: []RichTextRun{{Text: s}, {Text: s}}}))
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "B7", Author: "Excelize", Text: s[:TotalCellChars-1], Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
// Test add comment on a cell which already exists a comment
assert.Equal(t, f.AddComment("Sheet2", Comment{Cell: "B7", Author: s, Text: s}), newAddCommentError("B7"))
// Test add comment on not exists worksheet
assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist")
// Test add comment on with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
comments, err := f.GetComments("Sheet1")
assert.NoError(t, err)
assert.Len(t, comments, 2)
comments, err = f.GetComments("Sheet2")
assert.NoError(t, err)
assert.Len(t, comments, 1)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx")))
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`Excelize: Excelize: `))
comments, err = f.GetComments("Sheet1")
assert.NoError(t, err)
assert.Len(t, comments, 2)
comments, err = f.GetComments("Sheet2")
assert.NoError(t, err)
assert.Len(t, comments, 1)
comments, err = NewFile().GetComments("Sheet1")
assert.NoError(t, err)
assert.Len(t, comments, 0)
// Test add comments with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.AddComment("Sheet:1", Comment{Cell: "A1", Author: "Excelize", Text: "This is a comment."}))
// Test add comments with unsupported charset
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
_, err = f.GetComments("Sheet2")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test add comments with unsupported charset
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8")
// Test add comments with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8")
// Test get comments on not exists worksheet
comments, err = f.GetComments("SheetN")
assert.Len(t, comments, 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
}
func TestDeleteComment(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A40", Text: "Excelize: This is a comment1."}))
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A41", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment2."}}}))
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C41", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment3."}}}))
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C42", Paragraph: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment4."}}}))
assert.NoError(t, f.DeleteComment("Sheet2", "A40"))
comments, err := f.GetComments("Sheet2")
assert.NoError(t, err)
assert.Len(t, comments, 3)
comments, err = NewFile().GetComments("Sheet1")
assert.NoError(t, err)
assert.Len(t, comments, 0)
// Test delete comment with invalid sheet name
assert.Equal(t, ErrSheetNameInvalid, f.DeleteComment("Sheet:1", "A1"))
// Test delete all comments in a worksheet
assert.NoError(t, f.DeleteComment("Sheet2", "A41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C42"))
comments, err = f.GetComments("Sheet2")
assert.NoError(t, err)
assert.EqualValues(t, 0, len(comments))
// Test delete comment on not exists worksheet
assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist")
// Test delete comment with worksheet part
f.Pkg.Delete("xl/worksheets/sheet1.xml")
assert.NoError(t, f.DeleteComment("Sheet1", "A22"))
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteComment("Sheet2", "A41"), "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
// Test delete comment on a no comments worksheet
assert.NoError(t, f.DeleteComment("Sheet1", "A1"))
}
func TestDecodeVMLDrawingReader(t *testing.T) {
f := NewFile()
path := "xl/drawings/vmlDrawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err := f.decodeVMLDrawingReader(path)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestCommentsReader(t *testing.T) {
f := NewFile()
// Test read comments with unsupported charset
path := "xl/comments1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err := f.commentsReader(path)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestCountComments(t *testing.T) {
f := NewFile()
f.Comments["xl/comments1.xml"] = nil
assert.Equal(t, f.countComments(), 1)
}
func TestAddDrawingVML(t *testing.T) {
// Test addDrawingVML with illegal cell reference
f := NewFile()
assert.Equal(t, f.addDrawingVML(0, "", &vmlOptions{FormControl: FormControl{Cell: "*"}}), newCellNameToCoordinatesError("*", newInvalidCellNameError("*")))
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", &vmlOptions{sheet: "Sheet1", FormControl: FormControl{Cell: "A1"}}), "XML syntax error on line 1: invalid UTF-8")
}
func TestFormControl(t *testing.T) {
f := NewFile()
formControls := []FormControl{
{
Cell: "D1", Type: FormControlButton, Macro: "Button1_Click",
},
{
Cell: "A1", Type: FormControlButton, Macro: "Button1_Click",
Width: 140, Height: 60, Text: "Button 1\n",
Paragraph: []RichTextRun{
{
Font: &Font{
Bold: true,
Italic: true,
Underline: "single",
Family: "Times New Roman",
Size: 14,
Color: "777777",
},
Text: "C1=A1+B1",
},
},
Format: GraphicOptions{PrintObject: boolPtr(true), Positioning: "absolute"},
},
{
Cell: "A5", Type: FormControlCheckBox, Text: "Check Box 1",
Checked: true, Format: GraphicOptions{
PrintObject: boolPtr(false), Positioning: "oneCell",
},
},
{
Cell: "A6", Type: FormControlCheckBox, Text: "Check Box 2",
CellLink: "C5", Format: GraphicOptions{Positioning: "twoCell"},
},
{
Cell: "A7", Type: FormControlOptionButton, Text: "Option Button 1", Checked: true,
},
{
Cell: "A8", Type: FormControlOptionButton, Text: "Option Button 2",
},
{
Cell: "D3", Type: FormControlGroupBox, Text: "Group Box 1",
Width: 140, Height: 60,
},
{
Cell: "A9", Type: FormControlLabel, Text: "Label 1", Width: 140,
},
{
Cell: "C5", Type: FormControlSpinButton, Width: 40, Height: 60,
CurrentVal: 7, MinVal: 5, MaxVal: 10, IncChange: 1, CellLink: "C2",
},
{
Cell: "D7", Type: FormControlScrollBar, Width: 140, Height: 20,
CurrentVal: 50, MinVal: 10, MaxVal: 100, IncChange: 1, PageChange: 1, Horizontally: true, CellLink: "C3",
},
{
Cell: "G1", Type: FormControlScrollBar, Width: 20, Height: 140,
CurrentVal: 50, MinVal: 1000, MaxVal: 100, IncChange: 1, PageChange: 1, CellLink: "C4",
},
}
for _, formCtrl := range formControls {
assert.NoError(t, f.AddFormControl("Sheet1", formCtrl))
}
// Test get from controls
result, err := f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, result, 11)
for i, formCtrl := range formControls {
assert.Equal(t, formCtrl.Type, result[i].Type)
assert.Equal(t, formCtrl.Cell, result[i].Cell)
assert.Equal(t, formCtrl.Macro, result[i].Macro)
assert.Equal(t, formCtrl.Checked, result[i].Checked)
assert.Equal(t, formCtrl.CurrentVal, result[i].CurrentVal)
assert.Equal(t, formCtrl.MinVal, result[i].MinVal)
assert.Equal(t, formCtrl.MaxVal, result[i].MaxVal)
assert.Equal(t, formCtrl.IncChange, result[i].IncChange)
assert.Equal(t, formCtrl.Horizontally, result[i].Horizontally)
assert.Equal(t, formCtrl.CellLink, result[i].CellLink)
assert.Equal(t, formCtrl.Text, result[i].Text)
assert.Equal(t, len(formCtrl.Paragraph), len(result[i].Paragraph))
}
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")}))
file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin"))
assert.NoError(t, err)
assert.NoError(t, f.AddVBAProject(file))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddFormControl.xlsm")))
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
assert.NoError(t, err)
// Test get from controls before add form controls
result, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, result, 11)
// Test add from control to a worksheet which already contains form controls
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
Cell: "D4", Type: FormControlButton, Macro: "Button1_Click",
Paragraph: []RichTextRun{{Font: &Font{Underline: "double"}, Text: "Button 2"}},
}))
// Test get from controls after add form controls
result, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, result, 12)
// Test add unsupported form control
assert.Equal(t, f.AddFormControl("Sheet1", FormControl{
Cell: "A1", Type: 0x37, Macro: "Button1_Click",
}), ErrParameterInvalid)
// Test add form control on not exists worksheet
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.AddFormControl("SheetN", FormControl{
Cell: "A1", Type: FormControlButton, Macro: "Button1_Click",
}))
// Test add form control with invalid positioning types
assert.Equal(t, newInvalidOptionalValue("Positioning", "x", supportedPositioning),
f.AddFormControl("Sheet1", FormControl{
Cell: "A1", Type: FormControlButton,
Format: GraphicOptions{Positioning: "x"},
}))
// Test add spin form control with illegal cell link reference
assert.Equal(t, f.AddFormControl("Sheet1", FormControl{
Cell: "C5", Type: FormControlSpinButton, CellLink: "*",
}), newCellNameToCoordinatesError("*", newInvalidCellNameError("*")))
// Test add spin form control with invalid scroll value
assert.Equal(t, f.AddFormControl("Sheet1", FormControl{
Cell: "C5", Type: FormControlSpinButton, CurrentVal: MaxFormControlValue + 1,
}), ErrFormControlValue)
assert.NoError(t, f.Close())
// Test delete form control
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
assert.NoError(t, err)
assert.NoError(t, f.DeleteFormControl("Sheet1", "D1"))
assert.NoError(t, f.DeleteFormControl("Sheet1", "A1"))
// Test get from controls after delete form controls
result, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, result, 9)
// Test delete form control on not exists worksheet
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.DeleteFormControl("SheetN", "A1"))
// Test delete form control with illegal cell link reference
assert.Equal(t, f.DeleteFormControl("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteFormControl.xlsm")))
assert.NoError(t, f.Close())
// Test delete form control with expected element
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
assert.Error(t, f.DeleteFormControl("Sheet1", "A1"), "XML syntax error on line 1: invalid UTF-8")
// Test delete form controls with invalid shape anchor
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: "0"}},
}
assert.Equal(t, ErrParameterInvalid, f.DeleteFormControl("Sheet1", "A1"))
assert.NoError(t, f.Close())
// Test delete form control on a worksheet without form control
f = NewFile()
assert.NoError(t, f.DeleteFormControl("Sheet1", "A1"))
// Test get form controls on a worksheet without form control
_, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
// Test get form controls on not exists worksheet
_, err = f.GetFormControls("SheetN")
assert.Equal(t, ErrSheetNotExist{"SheetN"}, err)
// Test get form controls with unsupported charset VML drawing
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
_, err = f.GetFormControls("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get form controls with unsupported shape type
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "_x0000_t202"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, formControls, 0)
// Test get form controls with bold font format
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: "Text
0,0,0,0,0,0,0,0"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.True(t, formControls[0].Paragraph[0].Font.Bold)
// Test get form controls with italic font format
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: "Text
0,0,0,0,0,0,0,0"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.True(t, formControls[0].Paragraph[0].Font.Italic)
// Test get form controls with font format
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: "Text
0,0,0,0,0,0,0,0"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "Calibri", formControls[0].Paragraph[0].Font.Family)
assert.Equal(t, 14.0, formControls[0].Paragraph[0].Font.Size)
assert.Equal(t, "#777777", formControls[0].Paragraph[0].Font.Color)
// Test get form controls with italic font format
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: "Text
0,0,0,0,0,0,0,0"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.True(t, formControls[0].Paragraph[0].Font.Italic)
// Test get form controls with invalid column number
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d,0,0,0,0,0,0,0", MaxColumns)}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.Equal(t, err, ErrColumnNumber)
assert.Len(t, formControls, 0)
// Test get form controls with comment (Note) shape type
f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{
Shape: []decodeShape{{Type: "#_x0000_t201", Val: ""}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, formControls, 0)
// Test get form controls with unsupported shape type
f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{
Shape: []xlsxShape{{Type: "_x0000_t202"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, formControls, 0)
// Test get form controls with invalid column number
f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{
Shape: []xlsxShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d,0,0,0,0,0,0,0", MaxColumns)}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.Equal(t, err, ErrColumnNumber)
assert.Len(t, formControls, 0)
// Test get form controls with invalid shape anchor
f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{
Shape: []xlsxShape{{Type: "#_x0000_t201", Val: "x,0,0,0,0,0,0,0"}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.Equal(t, ErrColumnNumber, err)
assert.Len(t, formControls, 0)
// Test get form controls with comment (Note) shape type
f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{
Shape: []xlsxShape{{Type: "#_x0000_t201", Val: ""}},
}
formControls, err = f.GetFormControls("Sheet1")
assert.NoError(t, err)
assert.Len(t, formControls, 0)
assert.NoError(t, f.Close())
}
func TestExtractFormControl(t *testing.T) {
// Test extract form control with unsupported charset
_, err := extractFormControl(string(MacintoshCyrillicCharset))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestAddHeaderFooterImage(t *testing.T) {
f, sheet, wb := NewFile(), "Sheet1", filepath.Join("test", "TestAddHeaderFooterImage.xlsx")
headerFooterOptions := HeaderFooterOptions{
DifferentFirst: true,
OddHeader: "&L&GExcelize&C&G&R&G",
OddFooter: "&L&GExcelize&C&G&R&G",
FirstHeader: "&L&GExcelize&C&G&R&G",
FirstFooter: "&L&GExcelize&C&G&R&G",
}
assert.NoError(t, f.SetHeaderFooter(sheet, &headerFooterOptions))
assert.NoError(t, f.SetSheetView(sheet, -1, &ViewOptions{View: stringPtr("pageLayout")}))
images := map[string][]byte{
".wmf": nil, ".tif": nil, ".png": nil,
".jpg": nil, ".gif": nil, ".emz": nil, ".emf": nil,
}
for ext := range images {
img, err := os.ReadFile(filepath.Join("test", "images", "excel"+ext))
assert.NoError(t, err)
images[ext] = img
}
for _, opt := range []struct {
position HeaderFooterImagePositionType
file []byte
isFooter bool
firstPage bool
ext string
}{
{position: HeaderFooterImagePositionLeft, file: images[".tif"], firstPage: true, ext: ".tif"},
{position: HeaderFooterImagePositionCenter, file: images[".gif"], firstPage: true, ext: ".gif"},
{position: HeaderFooterImagePositionRight, file: images[".png"], firstPage: true, ext: ".png"},
{position: HeaderFooterImagePositionLeft, file: images[".emf"], isFooter: true, firstPage: true, ext: ".emf"},
{position: HeaderFooterImagePositionCenter, file: images[".wmf"], isFooter: true, firstPage: true, ext: ".wmf"},
{position: HeaderFooterImagePositionRight, file: images[".emz"], isFooter: true, firstPage: true, ext: ".emz"},
{position: HeaderFooterImagePositionLeft, file: images[".png"], ext: ".png"},
{position: HeaderFooterImagePositionCenter, file: images[".png"], ext: ".png"},
{position: HeaderFooterImagePositionRight, file: images[".png"], ext: ".png"},
{position: HeaderFooterImagePositionLeft, file: images[".tif"], isFooter: true, ext: ".tif"},
{position: HeaderFooterImagePositionCenter, file: images[".tif"], isFooter: true, ext: ".tif"},
{position: HeaderFooterImagePositionRight, file: images[".tif"], isFooter: true, ext: ".tif"},
} {
assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
Position: opt.position,
File: opt.file,
IsFooter: opt.isFooter,
FirstPage: opt.firstPage,
Extension: opt.ext,
Width: "50pt",
Height: "32pt",
}))
}
assert.NoError(t, f.SetCellValue(sheet, "A1", "Example"))
// Test add header footer image with not exist sheet
assert.EqualError(t, f.AddHeaderFooterImage("SheetN", nil), "sheet SheetN does not exist")
// Test add header footer image with unsupported file type
assert.Equal(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
Extension: "jpg",
}), ErrImgExt)
assert.NoError(t, f.SaveAs(wb))
assert.NoError(t, f.Close())
// Test change already exist header image with the different image
f, err := OpenFile(wb)
assert.NoError(t, err)
assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
File: images[".jpg"],
FirstPage: true,
Extension: ".jpg",
Width: "50pt",
Height: "32pt",
}))
assert.NoError(t, f.Save())
assert.NoError(t, f.Close())
// Test add header image with unsupported charset VML drawing
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
File: images[".jpg"],
Extension: ".jpg",
Width: "50pt",
Height: "32pt",
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test set legacy drawing header/footer with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
Extension: ".png",
File: images[".png"],
Width: "50pt",
Height: "32pt",
}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
================================================
FILE: workbook.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"bytes"
"encoding/xml"
"io"
"path/filepath"
"reflect"
"strconv"
"strings"
)
// SetWorkbookProps provides a function to sets workbook properties.
func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
if opts == nil {
return nil
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.WorkbookPr == nil {
wb.WorkbookPr = new(xlsxWorkbookPr)
}
setNoPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"},
reflect.ValueOf(*opts), reflect.ValueOf(wb.WorkbookPr).Elem())
return err
}
// GetWorkbookProps provides a function to gets workbook properties.
func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
var opts WorkbookPropsOptions
wb, err := f.workbookReader()
if err != nil {
return opts, err
}
if wb.WorkbookPr == nil {
return opts, err
}
setPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"},
reflect.ValueOf(*wb.WorkbookPr), reflect.ValueOf(&opts).Elem())
return opts, err
}
// SetCalcProps provides a function to sets calculation properties. Optional
// value of "CalcMode" property is: "manual", "auto" or "autoNoTable". Optional
// value of "RefMode" property is: "A1" or "R1C1".
func (f *File) SetCalcProps(opts *CalcPropsOptions) error {
if opts == nil {
return nil
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.CalcPr == nil {
wb.CalcPr = new(xlsxCalcPr)
}
if opts.CalcMode != nil && inStrSlice(supportedCalcMode, *opts.CalcMode, true) == -1 {
return newInvalidOptionalValue("CalcMode", *opts.CalcMode, supportedCalcMode)
}
if opts.RefMode != nil && inStrSlice(supportedRefMode, *opts.RefMode, true) == -1 {
return newInvalidOptionalValue("RefMode", *opts.RefMode, supportedRefMode)
}
setNoPtrFieldsVal([]string{
"CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate",
"IterateDelta",
"CalcMode", "RefMode",
}, reflect.ValueOf(*opts), reflect.ValueOf(wb.CalcPr).Elem())
if opts.CalcID != nil {
wb.CalcPr.CalcID = int(*opts.CalcID)
}
if opts.ConcurrentManualCount != nil {
wb.CalcPr.ConcurrentManualCount = int(*opts.ConcurrentManualCount)
}
if opts.IterateCount != nil {
wb.CalcPr.IterateCount = int(*opts.IterateCount)
}
wb.CalcPr.ConcurrentCalc = opts.ConcurrentCalc
return err
}
// GetCalcProps provides a function to gets calculation properties.
func (f *File) GetCalcProps() (CalcPropsOptions, error) {
var opts CalcPropsOptions
wb, err := f.workbookReader()
if err != nil {
return opts, err
}
if wb.CalcPr == nil {
return opts, err
}
setPtrFieldsVal([]string{
"CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate",
"IterateDelta",
"CalcMode", "RefMode",
}, reflect.ValueOf(*wb.CalcPr), reflect.ValueOf(&opts).Elem())
opts.CalcID = uintPtr(uint(wb.CalcPr.CalcID))
opts.ConcurrentManualCount = uintPtr(uint(wb.CalcPr.ConcurrentManualCount))
opts.IterateCount = uintPtr(uint(wb.CalcPr.IterateCount))
opts.ConcurrentCalc = wb.CalcPr.ConcurrentCalc
return opts, err
}
// ProtectWorkbook provides a function to prevent other users from viewing
// hidden worksheets, adding, moving, deleting, or hiding worksheets, and
// renaming worksheets in a workbook. The optional field AlgorithmName
// specified hash algorithm, support XOR, MD4, MD5, SHA-1, SHA2-56, SHA-384,
// and SHA-512 currently, if no hash algorithm specified, will be using the XOR
// algorithm as default. The generated workbook only works on Microsoft Office
// 2007 and later. For example, protect workbook with protection settings:
//
// err := f.ProtectWorkbook(&excelize.WorkbookProtectionOptions{
// Password: "password",
// LockStructure: true,
// })
func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.WorkbookProtection == nil {
wb.WorkbookProtection = new(xlsxWorkbookProtection)
}
if opts == nil {
opts = &WorkbookProtectionOptions{}
}
wb.WorkbookProtection = &xlsxWorkbookProtection{
LockStructure: opts.LockStructure,
LockWindows: opts.LockWindows,
}
if opts.Password != "" {
if opts.AlgorithmName == "" {
opts.AlgorithmName = "SHA-512"
}
hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(workbookProtectionSpinCount))
if err != nil {
return err
}
wb.WorkbookProtection.WorkbookAlgorithmName = opts.AlgorithmName
wb.WorkbookProtection.WorkbookSaltValue = saltValue
wb.WorkbookProtection.WorkbookHashValue = hashValue
wb.WorkbookProtection.WorkbookSpinCount = int(workbookProtectionSpinCount)
}
return err
}
// UnprotectWorkbook provides a function to remove protection for workbook,
// specified the optional password parameter to remove workbook protection with
// password verification.
func (f *File) UnprotectWorkbook(password ...string) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
// password verification
if len(password) > 0 {
if wb.WorkbookProtection == nil {
return ErrUnprotectWorkbook
}
if wb.WorkbookProtection.WorkbookAlgorithmName != "" {
// check with given salt value
hashValue, _, err := genISOPasswdHash(password[0], wb.WorkbookProtection.WorkbookAlgorithmName, wb.WorkbookProtection.WorkbookSaltValue, wb.WorkbookProtection.WorkbookSpinCount)
if err != nil {
return err
}
if wb.WorkbookProtection.WorkbookHashValue != hashValue {
return ErrUnprotectWorkbookPassword
}
}
}
wb.WorkbookProtection = nil
return err
}
// setWorkbook update workbook property of the spreadsheet. Maximum 31
// characters are allowed in sheet title.
func (f *File) setWorkbook(name string, sheetID, rid int) {
wb, _ := f.workbookReader()
wb.Sheets.Sheet = append(wb.Sheets.Sheet, xlsxSheet{
Name: name,
SheetID: sheetID,
ID: "rId" + strconv.Itoa(rid),
})
}
// getWorkbookPath provides a function to get the path of the workbook.xml in
// the spreadsheet.
func (f *File) getWorkbookPath() (path string) {
if rels, _ := f.relsReader(defaultXMLPathRels); rels != nil {
rels.mu.Lock()
defer rels.mu.Unlock()
for _, rel := range rels.Relationships {
if rel.Type == SourceRelationshipOfficeDocument {
path = strings.TrimPrefix(rel.Target, "/")
return
}
}
}
return
}
// getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels
// in the spreadsheet.
func (f *File) getWorkbookRelsPath() (path string) {
wbPath := f.getWorkbookPath()
wbDir := filepath.Dir(wbPath)
if wbDir == "." {
path = "_rels/" + filepath.Base(wbPath) + ".rels"
return
}
path = strings.TrimPrefix(filepath.Dir(wbPath)+"/_rels/"+filepath.Base(wbPath)+".rels", "/")
return
}
// deleteWorkbookRels provides a function to delete relationships in
// xl/_rels/workbook.xml.rels by given type and target.
func (f *File) deleteWorkbookRels(relType, relTarget string) (string, error) {
var rID string
rels, err := f.relsReader(f.getWorkbookRelsPath())
if err != nil {
return rID, err
}
if rels == nil {
rels = &xlsxRelationships{}
}
for k, v := range rels.Relationships {
if v.Type == relType && v.Target == relTarget {
rID = v.ID
rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...)
}
}
return rID, err
}
// workbookReader provides a function to get the pointer to the workbook.xml
// structure after deserialization.
func (f *File) workbookReader() (*xlsxWorkbook, error) {
var err error
if f.WorkBook == nil {
wbPath := f.getWorkbookPath()
f.WorkBook = new(xlsxWorkbook)
if attrs, ok := f.xmlAttr.Load(wbPath); !ok {
d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath))))
if attrs == nil {
attrs = []xml.Attr{}
}
attrs = append(attrs.([]xml.Attr), getRootElement(d)...)
f.xmlAttr.Store(wbPath, attrs)
f.addNameSpaces(wbPath, SourceRelationship)
}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))).
Decode(f.WorkBook); err != nil && err != io.EOF {
return f.WorkBook, err
}
}
return f.WorkBook, err
}
// workBookWriter provides a function to save workbook.xml after serialize
// structure.
func (f *File) workBookWriter() {
if f.WorkBook != nil {
if f.WorkBook.DecodeAlternateContent != nil {
f.WorkBook.AlternateContent = &xlsxAlternateContent{
Content: f.WorkBook.DecodeAlternateContent.Content,
XMLNSMC: SourceRelationshipCompatibility.Value,
}
}
f.WorkBook.DecodeAlternateContent = nil
output, _ := xml.Marshal(f.WorkBook)
f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output)))
}
}
// setContentTypePartRelsExtensions provides a function to set the content type
// for relationship parts and the Main Document part.
func (f *File) setContentTypePartRelsExtensions() error {
var rels bool
content, err := f.contentTypesReader()
if err != nil {
return err
}
for _, v := range content.Defaults {
if v.Extension == "rels" {
rels = true
}
}
if !rels {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: "rels",
ContentType: ContentTypeRelationships,
})
}
return err
}
// setContentTypePartImageExtensions provides a function to set the content type
// for relationship parts and the Main Document part.
func (f *File) setContentTypePartImageExtensions() error {
imageTypes := map[string]string{
"bmp": "image/", "ico": "image/x-", "jpeg": "image/", "png": "image/", "gif": "image/",
"svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-",
"emz": "image/x-", "wmz": "image/x-",
}
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for _, file := range content.Defaults {
delete(imageTypes, file.Extension)
}
for extension, prefix := range imageTypes {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: extension,
ContentType: prefix + extension,
})
}
return err
}
// setContentTypePartVMLExtensions provides a function to set the content type
// for relationship parts and the Main Document part.
func (f *File) setContentTypePartVMLExtensions() error {
var vml bool
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for _, v := range content.Defaults {
if v.Extension == "vml" {
vml = true
}
}
if !vml {
content.Defaults = append(content.Defaults, xlsxDefault{
Extension: "vml",
ContentType: ContentTypeVML,
})
}
return err
}
// addContentTypePart provides a function to add content type part relationships
// in the file [Content_Types].xml by given index and content type.
func (f *File) addContentTypePart(index int, contentType string) error {
setContentType := map[string]func() error{
"comments": f.setContentTypePartVMLExtensions,
"drawings": f.setContentTypePartImageExtensions,
}
partNames := map[string]string{
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
"customProperties": "/docProps/custom.xml",
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
"sharedStrings": "/xl/sharedStrings.xml",
"slicer": "/xl/slicers/slicer" + strconv.Itoa(index) + ".xml",
"slicerCache": "/xl/slicerCaches/slicerCache" + strconv.Itoa(index) + ".xml",
}
contentTypes := map[string]string{
"chart": ContentTypeDrawingML,
"chartsheet": ContentTypeSpreadSheetMLChartsheet,
"comments": ContentTypeSpreadSheetMLComments,
"customProperties": ContentTypeCustomProperties,
"drawings": ContentTypeDrawing,
"table": ContentTypeSpreadSheetMLTable,
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
"sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
"slicer": ContentTypeSlicer,
"slicerCache": ContentTypeSlicerCache,
}
s, ok := setContentType[contentType]
if ok {
if err := s(); err != nil {
return err
}
}
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for _, v := range content.Overrides {
if v.PartName == partNames[contentType] {
return err
}
}
content.Overrides = append(content.Overrides, xlsxOverride{
PartName: partNames[contentType],
ContentType: contentTypes[contentType],
})
return f.setContentTypePartRelsExtensions()
}
// removeContentTypesPart provides a function to remove relationships by given
// content type and part name in the file [Content_Types].xml.
func (f *File) removeContentTypesPart(contentType, partName string) error {
if !strings.HasPrefix(partName, "/") {
partName = "/xl/" + partName
}
content, err := f.contentTypesReader()
if err != nil {
return err
}
content.mu.Lock()
defer content.mu.Unlock()
for k, v := range content.Overrides {
if v.PartName == partName && v.ContentType == contentType {
content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
}
}
return err
}
================================================
FILE: workbook_test.go
================================================
package excelize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestWorkbookProps(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetWorkbookProps(nil))
wb, err := f.workbookReader()
assert.NoError(t, err)
wb.WorkbookPr = nil
expected := WorkbookPropsOptions{
Date1904: boolPtr(true),
FilterPrivacy: boolPtr(true),
CodeName: stringPtr("code"),
}
assert.NoError(t, f.SetWorkbookProps(&expected))
opts, err := f.GetWorkbookProps()
assert.NoError(t, err)
assert.Equal(t, expected, opts)
wb.WorkbookPr = nil
opts, err = f.GetWorkbookProps()
assert.NoError(t, err)
assert.Equal(t, WorkbookPropsOptions{}, opts)
// Test set workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8")
// Test get workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetWorkbookProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestCalcProps(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCalcProps(nil))
wb, err := f.workbookReader()
assert.NoError(t, err)
wb.CalcPr = nil
expected := CalcPropsOptions{
FullCalcOnLoad: boolPtr(true),
CalcID: uintPtr(122211),
ConcurrentManualCount: uintPtr(5),
IterateCount: uintPtr(10),
ConcurrentCalc: boolPtr(true),
}
assert.NoError(t, f.SetCalcProps(&expected))
opts, err := f.GetCalcProps()
assert.NoError(t, err)
assert.Equal(t, expected, opts)
wb.CalcPr = nil
opts, err = f.GetCalcProps()
assert.NoError(t, err)
assert.Equal(t, CalcPropsOptions{}, opts)
// Test set calculation properties with unsupported optional value
assert.Equal(t, newInvalidOptionalValue("CalcMode", "AUTO", supportedCalcMode), f.SetCalcProps(&CalcPropsOptions{CalcMode: stringPtr("AUTO")}))
assert.Equal(t, newInvalidOptionalValue("RefMode", "a1", supportedRefMode), f.SetCalcProps(&CalcPropsOptions{RefMode: stringPtr("a1")}))
// Test set calculation properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCalcProps(&expected), "XML syntax error on line 1: invalid UTF-8")
// Test get calculation properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetCalcProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteWorkbookRels(t *testing.T) {
f := NewFile()
// Test delete pivot table without worksheet relationships
f.Relationships.Delete("xl/_rels/workbook.xml.rels")
f.Pkg.Delete("xl/_rels/workbook.xml.rels")
rID, err := f.deleteWorkbookRels("", "")
assert.Empty(t, rID)
assert.NoError(t, err)
}
================================================
FILE: xmlApp.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// AppProperties directly maps the document application properties.
type AppProperties struct {
Application string
ScaleCrop bool
DocSecurity int
Company string
LinksUpToDate bool
HyperlinksChanged bool
AppVersion string
}
// xlsxProperties specifies to an OOXML document properties such as the
// template used, the number of pages and words, and the application name and
// version.
type xlsxProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"`
Vt string `xml:"xmlns:vt,attr"`
Template string `xml:",omitempty"`
Manager string `xml:",omitempty"`
Company string `xml:",omitempty"`
Pages int `xml:",omitempty"`
Words int `xml:",omitempty"`
Characters int `xml:",omitempty"`
PresentationFormat string `xml:",omitempty"`
Lines int `xml:",omitempty"`
Paragraphs int `xml:",omitempty"`
Slides int `xml:",omitempty"`
Notes int `xml:",omitempty"`
TotalTime int `xml:",omitempty"`
HiddenSlides int `xml:",omitempty"`
MMClips int `xml:",omitempty"`
ScaleCrop bool `xml:",omitempty"`
HeadingPairs *xlsxVectorVariant
TitlesOfParts *xlsxVectorLpstr
LinksUpToDate bool `xml:",omitempty"`
CharactersWithSpaces int `xml:",omitempty"`
SharedDoc bool `xml:",omitempty"`
HyperlinkBase string `xml:",omitempty"`
HLinks *xlsxVectorVariant
HyperlinksChanged bool `xml:",omitempty"`
DigSig *xlsxDigSig
Application string `xml:",omitempty"`
AppVersion string `xml:",omitempty"`
DocSecurity int `xml:",omitempty"`
}
// xlsxVectorVariant specifies the set of hyperlinks that were in this
// document when last saved.
type xlsxVectorVariant struct {
Content string `xml:",innerxml"`
}
type xlsxVectorLpstr struct {
Content string `xml:",innerxml"`
}
// xlsxDigSig contains the signature of a digitally signed document.
type xlsxDigSig struct {
Content string `xml:",innerxml"`
}
================================================
FILE: xmlCalcChain.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxCalcChain directly maps the calcChain element. This element represents
// the root of the calculation chain.
type xlsxCalcChain struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"`
C []xlsxCalcChainC `xml:"c"`
}
// xlsxCalcChainC directly maps the c element.
//
// Attributes | Attributes
// --------------------------+----------------------------------------------------------
// a (Array) | A Boolean flag indicating whether the cell's formula
// | is an array formula. True if this cell's formula is
// | an array formula, false otherwise. If there is a
// | conflict between this attribute and the t attribute
// | of the f element (§18.3.1.40), the t attribute takes
// | precedence. The possible values for this attribute
// | are defined by the W3C XML Schema boolean datatype.
// |
// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
// | omitted, it is assumed to be the same as the i value
// | of the previous cell.The possible values for this
// | attribute are defined by the W3C XML Schema int datatype.
// |
// l (New Dependency Level) | A Boolean flag indicating that the cell's formula
// | starts a new dependency level. True if the formula
// | starts a new dependency level, false otherwise.
// | Starting a new dependency level means that all
// | concurrent calculations, and child calculations, shall
// | be completed - and the cells have new values - before
// | the calc chain can continue. In other words, this
// | dependency level might depend on levels that came before
// | it, and any later dependency levels might depend on
// | this level; but not later dependency levels can have
// | any calculations started until this dependency level
// | completes.The possible values for this attribute are
// | defined by the W3C XML Schema boolean datatype.
// |
// r (Cell Reference) | An A-1 style reference to a cell.The possible values
// | for this attribute are defined by the ST_CellRef
// | simple type (§18.18.7).
// |
// s (Child Chain) | A Boolean flag indicating whether the cell's formula
// | is on a child chain. True if this cell is part of a
// | child chain, false otherwise. If this is omitted, it
// | is assumed to be the same as the s value of the
// | previous cell .A child chain is a list of calculations
// | that occur which depend on the parent to the chain.
// | There shall not be cross dependencies between child
// | chains. Child chains are not the same as dependency
// | levels - a child chain and its parent are all on the
// | same dependency level. Child chains are series of
// | calculations that can be independently farmed out to
// | other threads or processors.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
// |
// t (New Thread) | A Boolean flag indicating whether the cell's formula
// | starts a new thread. True if the cell's formula starts
// | a new thread, false otherwise.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
type xlsxCalcChainC struct {
R string `xml:"r,attr"`
I int `xml:"i,attr,omitempty"`
L bool `xml:"l,attr,omitempty"`
S bool `xml:"s,attr,omitempty"`
T bool `xml:"t,attr,omitempty"`
A bool `xml:"a,attr,omitempty"`
}
// xlsxVolTypes maps the volatileDependencies part provides a cache of data that
// supports Real Time Data (RTD) and CUBE functions in the workbook.
type xlsxVolTypes struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main volTypes"`
VolType []xlsxVolType `xml:"volType"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxVolType represents dependency information for a specific type of external
// data server.
type xlsxVolType struct {
Type string `xml:"type,attr"`
Main []xlsxVolMain `xml:"main"`
}
// xlsxVolMain represents dependency information for all topics within a
// volatile dependency type that share the same first string or function
// argument.
type xlsxVolMain struct {
First string `xml:"first,attr"`
Tp []xlsxVolTopic `xml:"tp"`
}
// xlsxVolTopic represents dependency information for all topics within a
// volatile dependency type that share the same first string or argument.
type xlsxVolTopic struct {
T string `xml:"t,attr,omitempty"`
V string `xml:"v"`
Stp []string `xml:"stp"`
Tr []xlsxVolTopicRef `xml:"tr"`
}
// xlsxVolTopicRef represents the reference to a cell that depends on this
// topic. Each topic can have one or more cells dependencies.
type xlsxVolTopicRef struct {
R string `xml:"r,attr"`
S int `xml:"s,attr"`
}
================================================
FILE: xmlChart.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxChartSpace directly maps the chartSpace element. The chart namespace in
// DrawingML is for representing visualizations of numeric data with column
// charts, pie charts, scatter charts, or other types of charts.
type xlsxChartSpace struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"`
XMLNSa string `xml:"xmlns:a,attr"`
Date1904 *attrValBool `xml:"date1904"`
Lang *attrValString `xml:"lang"`
RoundedCorners *attrValBool `xml:"roundedCorners"`
Chart cChart `xml:"chart"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
PrintSettings *cPrintSettings `xml:"printSettings"`
}
// cThicknessSpPr directly maps the element that specifies the thickness of
// the walls or floor as a percentage of the largest dimension of the plot
// volume and SpPr element.
type cThicknessSpPr struct {
Thickness *attrValInt `xml:"thickness"`
SpPr *cSpPr `xml:"spPr"`
}
// cChart (Chart) directly maps the chart element. This element specifies a
// title.
type cChart struct {
Title *cTitle `xml:"title"`
AutoTitleDeleted *cAutoTitleDeleted `xml:"autoTitleDeleted"`
View3D *cView3D `xml:"view3D"`
Floor *cThicknessSpPr `xml:"floor"`
SideWall *cThicknessSpPr `xml:"sideWall"`
BackWall *cThicknessSpPr `xml:"backWall"`
PlotArea *cPlotArea `xml:"plotArea"`
Legend *cLegend `xml:"legend"`
PlotVisOnly *attrValBool `xml:"plotVisOnly"`
DispBlanksAs *attrValString `xml:"dispBlanksAs"`
ShowDLblsOverMax *attrValBool `xml:"showDLblsOverMax"`
}
// cTitle (Title) directly maps the title element. This element specifies a
// title.
type cTitle struct {
Tx cTx `xml:"tx,omitempty"`
Layout string `xml:"layout,omitempty"`
Overlay *attrValBool `xml:"overlay"`
SpPr cSpPr `xml:"spPr,omitempty"`
TxPr cTxPr `xml:"txPr,omitempty"`
}
// cTx (Chart Text) directly maps the tx element. This element specifies text
// to use on a chart, including rich text formatting.
type cTx struct {
StrRef *cStrRef `xml:"strRef"`
Rich *cRich `xml:"rich,omitempty"`
}
// cRich (Rich Text) directly maps the rich element. This element contains a
// string with rich text formatting.
type cRich struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
LstStyle string `xml:"a:lstStyle,omitempty"`
P []aP `xml:"a:p"`
}
// aBodyPr (Body Properties) directly maps the a:bodyPr element. This element
// defines the body properties for the text body within a shape.
type aBodyPr struct {
Anchor string `xml:"anchor,attr,omitempty"`
AnchorCtr bool `xml:"anchorCtr,attr"`
Rot int `xml:"rot,attr"`
BIns float64 `xml:"bIns,attr,omitempty"`
CompatLnSpc bool `xml:"compatLnSpc,attr,omitempty"`
ForceAA bool `xml:"forceAA,attr,omitempty"`
FromWordArt bool `xml:"fromWordArt,attr,omitempty"`
HorzOverflow string `xml:"horzOverflow,attr,omitempty"`
LIns float64 `xml:"lIns,attr,omitempty"`
NumCol int `xml:"numCol,attr,omitempty"`
RIns float64 `xml:"rIns,attr,omitempty"`
RtlCol bool `xml:"rtlCol,attr,omitempty"`
SpcCol int `xml:"spcCol,attr,omitempty"`
SpcFirstLastPara bool `xml:"spcFirstLastPara,attr"`
TIns float64 `xml:"tIns,attr,omitempty"`
Upright bool `xml:"upright,attr,omitempty"`
Vert string `xml:"vert,attr,omitempty"`
VertOverflow string `xml:"vertOverflow,attr,omitempty"`
Wrap string `xml:"wrap,attr,omitempty"`
}
// aP (Paragraph) directly maps the a:p element. This element specifies a
// paragraph of content in the document.
type aP struct {
PPr *aPPr `xml:"a:pPr"`
R *aR `xml:"a:r"`
EndParaRPr *aEndParaRPr `xml:"a:endParaRPr"`
}
// aPPr (Paragraph Properties) directly maps the a:pPr element. This element
// specifies a set of paragraph properties which shall be applied to the
// contents of the parent paragraph after all style/numbering/table properties
// have been applied to the text. These properties are defined as direct
// formatting, since they are directly applied to the paragraph and supersede
// any formatting from styles.
type aPPr struct {
DefRPr aRPr `xml:"a:defRPr"`
}
// aSrgbClr (RGB Color Model - Hex Variant) specifies a color using the red,
// green, blue RGB color model. Red, green, and blue is expressed as sequence of
// hex digits, RRGGBB. A perceptual gamma of 2.2 is used.
type aSrgbClr struct {
Val *string `xml:"val,attr"`
Tint *attrValInt `xml:"a:tint"`
Shade *attrValInt `xml:"a:shade"`
Comp *attrValInt `xml:"a:comp"`
Inv *attrValInt `xml:"a:inv"`
Gray *attrValInt `xml:"a:gray"`
Alpha *attrValInt `xml:"a:alpha"`
AlphaOff *attrValInt `xml:"a:alphaOff"`
AlphaMod *attrValInt `xml:"a:alphaMod"`
Hue *attrValInt `xml:"a:hue"`
HueOff *attrValInt `xml:"a:hueOff"`
HueMod *attrValInt `xml:"a:hueMod"`
Sat *attrValInt `xml:"a:sat"`
SatOff *attrValInt `xml:"a:satOff"`
SatMod *attrValInt `xml:"a:satMod"`
Lum *attrValInt `xml:"a:lum"`
LumOff *attrValInt `xml:"a:lumOff"`
LumMod *attrValInt `xml:"a:lumMod"`
Red *attrValInt `xml:"a:red"`
RedOff *attrValInt `xml:"a:redOff"`
RedMod *attrValInt `xml:"a:redMod"`
Green *attrValInt `xml:"a:green"`
GreenOff *attrValInt `xml:"a:greenOff"`
GreenMod *attrValInt `xml:"a:greenMod"`
Blue *attrValInt `xml:"a:blue"`
BlueOff *attrValInt `xml:"a:blueOff"`
BlueMod *attrValInt `xml:"a:blueMod"`
Gamma *attrValInt `xml:"a:gamma"`
InvGamma *attrValInt `xml:"a:invGamma"`
}
// aSolidFill (Solid Fill) directly maps the solidFill element. This element
// specifies a solid color fill. The shape is filled entirely with the specified
// color.
type aSolidFill struct {
SchemeClr *aSchemeClr `xml:"a:schemeClr"`
SrgbClr *aSrgbClr `xml:"a:srgbClr"`
}
// aSchemeClr (Scheme Color) directly maps the a:schemeClr element. This
// element specifies a color bound to a user's theme. As with all elements which
// define a color, it is possible to apply a list of color transforms to the
// base color defined.
type aSchemeClr struct {
Val string `xml:"val,attr,omitempty"`
LumMod *attrValInt `xml:"a:lumMod"`
LumOff *attrValInt `xml:"a:lumOff"`
}
// attrValInt directly maps the val element with integer data type as an
// attribute.
type attrValInt struct {
Val *int `xml:"val,attr"`
}
// attrValFloat directly maps the val element with float64 data type as an
// attribute.
type attrValFloat struct {
Val *float64 `xml:"val,attr"`
}
// attrValBool directly maps the val element with boolean data type as an
// attribute.
type attrValBool struct {
Val *bool `xml:"val,attr"`
}
// attrValString directly maps the val element with string data type as an
// attribute.
type attrValString struct {
Val *string `xml:"val,attr"`
}
type xlsxCTTextFont struct {
Typeface string `xml:"typeface,attr"`
Panose string `xml:"panose,attr,omitempty"`
PitchFamily string `xml:"pitchFamily,attr,omitempty"`
Charset string `xml:"Charset,attr,omitempty"`
}
// aR directly maps the a:r element.
type aR struct {
RPr aRPr `xml:"a:rPr,omitempty"`
T string `xml:"a:t,omitempty"`
}
// aRPr (Run Properties) directly maps the rPr element. This element
// specifies a set of run properties which shall be applied to the contents of
// the parent run after all style formatting has been applied to the text. These
// properties are defined as direct formatting, since they are directly applied
// to the run and supersede any formatting from styles.
type aRPr struct {
AltLang string `xml:"altLang,attr,omitempty"`
B bool `xml:"b,attr"`
Baseline int `xml:"baseline,attr"`
Bmk string `xml:"bmk,attr,omitempty"`
Cap string `xml:"cap,attr,omitempty"`
Dirty bool `xml:"dirty,attr,omitempty"`
Err bool `xml:"err,attr,omitempty"`
I bool `xml:"i,attr"`
Kern int `xml:"kern,attr"`
Kumimoji bool `xml:"kumimoji,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"`
NoProof bool `xml:"noProof,attr,omitempty"`
NormalizeH bool `xml:"normalizeH,attr,omitempty"`
SmtClean bool `xml:"smtClean,attr,omitempty"`
SmtID uint64 `xml:"smtId,attr,omitempty"`
Spc int `xml:"spc,attr"`
Strike string `xml:"strike,attr,omitempty"`
Sz float64 `xml:"sz,attr,omitempty"`
U string `xml:"u,attr,omitempty"`
SolidFill *aSolidFill `xml:"a:solidFill"`
Latin *xlsxCTTextFont `xml:"a:latin"`
Ea *xlsxCTTextFont `xml:"a:ea"`
Cs *xlsxCTTextFont `xml:"a:cs"`
}
// cDTable (Data Table) directly maps the dTable element.
type cDTable struct {
ShowHorzBorder *attrValBool `xml:"showHorzBorder"`
ShowVertBorder *attrValBool `xml:"showVertBorder"`
ShowOutline *attrValBool `xml:"showOutline"`
ShowKeys *attrValBool `xml:"showKeys"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// cSpPr (Shape Properties) directly maps the spPr element. This element
// specifies the visual shape properties that can be applied to a shape. These
// properties include the shape fill, outline, geometry, effects, and 3D
// orientation.
type cSpPr struct {
NoFill *string `xml:"a:noFill"`
SolidFill *aSolidFill `xml:"a:solidFill"`
Ln *aLn `xml:"a:ln"`
Sp3D *aSp3D `xml:"a:sp3d"`
EffectLst *string `xml:"a:effectLst"`
}
// aSp3D (3-D Shape Properties) directly maps the a:sp3d element. This element
// defines the 3D properties associated with a particular shape in DrawingML.
// The 3D properties which can be applied to a shape are top and bottom bevels,
// a contour and an extrusion.
type aSp3D struct {
ContourW int `xml:"contourW,attr"`
ContourClr *aContourClr `xml:"a:contourClr"`
}
// aContourClr (Contour Color) directly maps the a:contourClr element. This
// element defines the color for the contour on a shape. The contour of a shape
// is a solid filled line which surrounds the outer edges of the shape.
type aContourClr struct {
SchemeClr *aSchemeClr `xml:"a:schemeClr"`
}
// aLn (Outline) directly maps the a:ln element. This element specifies an
// outline style that can be applied to a number of different objects such as
// shapes and text. The line allows for the specifying of many different types
// of outlines including even line dashes and bevels.
type aLn struct {
Algn string `xml:"algn,attr,omitempty"`
Cap string `xml:"cap,attr,omitempty"`
Cmpd string `xml:"cmpd,attr,omitempty"`
W int `xml:"w,attr,omitempty"`
NoFill *attrValString `xml:"a:noFill"`
Round string `xml:"a:round,omitempty"`
SolidFill *aSolidFill `xml:"a:solidFill"`
PrstDash *attrValString `xml:"a:prstDash"`
}
// cTxPr (Text Properties) directly maps the txPr element. This element
// specifies text formatting. The lstStyle element is not supported.
type cTxPr struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
LstStyle string `xml:"a:lstStyle,omitempty"`
P aP `xml:"a:p,omitempty"`
}
// aEndParaRPr (End Paragraph Run Properties) directly maps the a:endParaRPr
// element. This element specifies the text run properties that are to be used
// if another run is inserted after the last run specified. This effectively
// saves the run property state so that it can be applied when the user enters
// additional text. If this element is omitted, then the application can
// determine which default properties to apply. It is recommended that this
// element be specified at the end of the list of text runs within the paragraph
// so that an orderly list is maintained.
type aEndParaRPr struct {
Lang string `xml:"lang,attr"`
AltLang string `xml:"altLang,attr,omitempty"`
Sz int `xml:"sz,attr,omitempty"`
}
// cAutoTitleDeleted (Auto Title Is Deleted) directly maps the
// autoTitleDeleted element. This element specifies the title shall not be
// shown for this chart.
type cAutoTitleDeleted struct {
Val bool `xml:"val,attr"`
}
// cView3D (View In 3D) directly maps the view3D element. This element
// specifies the 3-D view of the chart.
type cView3D struct {
RotX *attrValInt `xml:"rotX"`
RotY *attrValInt `xml:"rotY"`
RAngAx *attrValInt `xml:"rAngAx"`
DepthPercent *attrValInt `xml:"depthPercent"`
Perspective *attrValInt `xml:"perspective"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// cPlotArea directly maps the plotArea element. This element specifies the
// plot area of the chart.
type cPlotArea struct {
Layout *string `xml:"layout"`
AreaChart []*cCharts `xml:"areaChart"`
Area3DChart []*cCharts `xml:"area3DChart"`
BarChart []*cCharts `xml:"barChart"`
Bar3DChart []*cCharts `xml:"bar3DChart"`
BubbleChart []*cCharts `xml:"bubbleChart"`
DoughnutChart []*cCharts `xml:"doughnutChart"`
LineChart []*cCharts `xml:"lineChart"`
Line3DChart []*cCharts `xml:"line3DChart"`
StockChart []*cCharts `xml:"stockChart"`
PieChart []*cCharts `xml:"pieChart"`
Pie3DChart []*cCharts `xml:"pie3DChart"`
OfPieChart []*cCharts `xml:"ofPieChart"`
RadarChart []*cCharts `xml:"radarChart"`
ScatterChart []*cCharts `xml:"scatterChart"`
Surface3DChart []*cCharts `xml:"surface3DChart"`
SurfaceChart []*cCharts `xml:"surfaceChart"`
CatAx []*cAxs `xml:"catAx"`
ValAx []*cAxs `xml:"valAx"`
DateAx []*cAxs `xml:"dateAx"`
SerAx []*cAxs `xml:"serAx"`
DTable *cDTable `xml:"dTable"`
SpPr *cSpPr `xml:"spPr"`
}
// cCharts specifies the common element of the chart.
type cCharts struct {
BarDir *attrValString `xml:"barDir"`
BubbleScale *attrValFloat `xml:"bubbleScale"`
Grouping *attrValString `xml:"grouping"`
RadarStyle *attrValString `xml:"radarStyle"`
ScatterStyle *attrValString `xml:"scatterStyle"`
OfPieType *attrValString `xml:"ofPieType"`
VaryColors *attrValBool `xml:"varyColors"`
Wireframe *attrValBool `xml:"wireframe"`
Ser *[]cSer `xml:"ser"`
SplitPos *attrValInt `xml:"splitPos"`
SerLines *attrValString `xml:"serLines"`
DLbls *cDLbls `xml:"dLbls"`
DropLines *cChartLines `xml:"dropLines"`
HiLowLines *cChartLines `xml:"hiLowLines"`
UpDownBars *cUpDownBars `xml:"upDownBars"`
GapWidth *attrValInt `xml:"gapWidth"`
Shape *attrValString `xml:"shape"`
HoleSize *attrValInt `xml:"holeSize"`
Smooth *attrValBool `xml:"smooth"`
Overlap *attrValInt `xml:"overlap"`
AxID []*attrValInt `xml:"axId"`
}
// cAxs directly maps the catAx and valAx element.
type cAxs struct {
AxID *attrValInt `xml:"axId"`
Scaling *cScaling `xml:"scaling"`
Delete *attrValBool `xml:"delete"`
AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"`
Title *cTitle `xml:"title"`
NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"`
TickLblPos *attrValString `xml:"tickLblPos"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
CrossAx *attrValInt `xml:"crossAx"`
Crosses *attrValString `xml:"crosses"`
CrossBetween *attrValString `xml:"crossBetween"`
MajorUnit *attrValFloat `xml:"majorUnit"`
MinorUnit *attrValFloat `xml:"minorUnit"`
Auto *attrValBool `xml:"auto"`
LblAlgn *attrValString `xml:"lblAlgn"`
LblOffset *attrValInt `xml:"lblOffset"`
TickLblSkip *attrValInt `xml:"tickLblSkip"`
TickMarkSkip *attrValInt `xml:"tickMarkSkip"`
NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"`
}
// cUpDownBars directly maps the upDownBars lement. This element specifies
// the up and down bars.
type cUpDownBars struct {
GapWidth *attrValString `xml:"gapWidth"`
UpBars *cChartLines `xml:"upBars"`
DownBars *cChartLines `xml:"downBars"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// cChartLines directly maps the chart lines content model.
type cChartLines struct {
SpPr *cSpPr `xml:"spPr"`
}
// cScaling directly maps the scaling element. This element contains
// additional axis settings.
type cScaling struct {
LogBase *attrValFloat `xml:"logBase"`
Orientation *attrValString `xml:"orientation"`
Max *attrValFloat `xml:"max"`
Min *attrValFloat `xml:"min"`
}
// cNumFmt (Numbering Format) directly maps the numFmt element. This element
// specifies number formatting for the parent element.
type cNumFmt struct {
FormatCode string `xml:"formatCode,attr"`
SourceLinked bool `xml:"sourceLinked,attr"`
}
// cSer directly maps the ser element. This element specifies a series on a
// chart.
type cSer struct {
IDx *attrValInt `xml:"idx"`
Order *attrValInt `xml:"order"`
Tx *cTx `xml:"tx"`
SpPr *cSpPr `xml:"spPr"`
DPt []*cDPt `xml:"dPt"`
DLbls *cDLbls `xml:"dLbls"`
Marker *cMarker `xml:"marker"`
InvertIfNegative *attrValBool `xml:"invertIfNegative"`
Cat *cCat `xml:"cat"`
Val *cVal `xml:"val"`
XVal *cCat `xml:"xVal"`
YVal *cVal `xml:"yVal"`
Smooth *attrValBool `xml:"smooth"`
BubbleSize *cVal `xml:"bubbleSize"`
Bubble3D *attrValBool `xml:"bubble3D"`
}
// cMarker (Marker) directly maps the marker element. This element specifies a
// data marker.
type cMarker struct {
Symbol *attrValString `xml:"symbol"`
Size *attrValInt `xml:"size"`
SpPr *cSpPr `xml:"spPr"`
}
// cDPt (Data Point) directly maps the dPt element. This element specifies a
// single data point.
type cDPt struct {
IDx *attrValInt `xml:"idx"`
Bubble3D *attrValBool `xml:"bubble3D"`
SpPr *cSpPr `xml:"spPr"`
}
// cCat (Category Axis Data) directly maps the cat element. This element
// specifies the data used for the category axis.
type cCat struct {
StrRef *cStrRef `xml:"strRef"`
}
// cStrRef (String Reference) directly maps the strRef element. This element
// specifies a reference to data for a single data label or title with a cache
// of the last values used.
type cStrRef struct {
F string `xml:"f"`
StrCache *cStrCache `xml:"strCache"`
}
// cStrCache (String Cache) directly maps the strCache element. This element
// specifies the last string data used for a chart.
type cStrCache struct {
Pt []*cPt `xml:"pt"`
PtCount *attrValInt `xml:"ptCount"`
}
// cPt directly maps the pt element. This element specifies data for a
// particular data point.
type cPt struct {
IDx int `xml:"idx,attr"`
V *string `xml:"v"`
}
// cVal directly maps the val element. This element specifies the data values
// which shall be used to define the location of data markers on a chart.
type cVal struct {
NumRef *cNumRef `xml:"numRef"`
}
// cNumRef directly maps the numRef element. This element specifies a
// reference to numeric data with a cache of the last values used.
type cNumRef struct {
F string `xml:"f"`
NumCache *cNumCache `xml:"numCache"`
}
// cNumCache directly maps the numCache element. This element specifies the
// last data shown on the chart for a series.
type cNumCache struct {
FormatCode string `xml:"formatCode"`
Pt []*cPt `xml:"pt"`
PtCount *attrValInt `xml:"ptCount"`
}
// cDLbls (Data Labels) directly maps the dLbls element. This element serves
// as a root element that specifies the settings for the data labels for an
// entire series or the entire chart. It contains child elements that specify
// the specific formatting and positioning settings.
type cDLbls struct {
NumFmt *cNumFmt `xml:"numFmt"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
DLblPos *attrValString `xml:"dLblPos"`
ShowLegendKey *attrValBool `xml:"showLegendKey"`
ShowVal *attrValBool `xml:"showVal"`
ShowCatName *attrValBool `xml:"showCatName"`
ShowSerName *attrValBool `xml:"showSerName"`
ShowPercent *attrValBool `xml:"showPercent"`
ShowBubbleSize *attrValBool `xml:"showBubbleSize"`
ShowLeaderLines *attrValBool `xml:"showLeaderLines"`
}
// cLegendEntry (Legend Entry) directly maps the legendEntry element. This
// element specifies the legend entry.
type cLegendEntry struct {
IDx *attrValInt `xml:"idx"`
TxPr *cTxPr `xml:"txPr"`
}
// cLegend (Legend) directly maps the legend element. This element specifies
// the legend.
type cLegend struct {
Layout *string `xml:"layout"`
LegendPos *attrValString `xml:"legendPos"`
LegendEntry []cLegendEntry `xml:"legendEntry"`
Overlay *attrValBool `xml:"overlay"`
SpPr *cSpPr `xml:"spPr"`
TxPr *cTxPr `xml:"txPr"`
}
// cPrintSettings directly maps the printSettings element. This element
// specifies the print settings for the chart.
type cPrintSettings struct {
HeaderFooter *string `xml:"headerFooter"`
PageMargins *cPageMargins `xml:"pageMargins"`
PageSetup *string `xml:"pageSetup"`
}
// cPageMargins directly maps the pageMargins element. This element specifies
// the page margins for a chart.
type cPageMargins struct {
B float64 `xml:"b,attr"`
Footer float64 `xml:"footer,attr"`
Header float64 `xml:"header,attr"`
L float64 `xml:"l,attr"`
R float64 `xml:"r,attr"`
T float64 `xml:"t,attr"`
}
// ChartNumFmt directly maps the number format settings of the chart.
type ChartNumFmt struct {
CustomNumFmt string
SourceLinked bool
}
// ChartAxis directly maps the format settings of the chart axis.
type ChartAxis struct {
None bool
DropLines bool
HighLowLines bool
MajorGridLines bool
MinorGridLines bool
MajorUnit float64
TickLabelPosition ChartTickLabelPositionType
TickLabelSkip int
ReverseOrder bool
Secondary bool
Maximum *float64
Minimum *float64
Alignment Alignment
Font Font
LogBase float64
NumFmt ChartNumFmt
Title []RichTextRun
axID int
}
// ChartDimension directly maps the dimension of the chart.
type ChartDimension struct {
Width uint
Height uint
}
// ChartUpDownBar directly maps the format settings of the stock chart up bars
// and down bars.
type ChartUpDownBar struct {
Fill Fill
Border ChartLine
}
// ChartPlotArea directly maps the format settings of the plot area.
type ChartPlotArea struct {
SecondPlotValues int
ShowBubbleSize bool
ShowCatName bool
ShowDataTable bool
ShowDataTableKeys bool
ShowLeaderLines bool
ShowPercent bool
ShowSerName bool
ShowVal bool
Fill Fill
UpBars ChartUpDownBar
DownBars ChartUpDownBar
NumFmt ChartNumFmt
}
// Chart directly maps the format settings of the chart.
type Chart struct {
Type ChartType
Series []ChartSeries
Format GraphicOptions
Dimension ChartDimension
Legend ChartLegend
Title []RichTextRun
VaryColors *bool
XAxis ChartAxis
YAxis ChartAxis
PlotArea ChartPlotArea
Fill Fill
Border ChartLine
ShowBlanksAs string
BubbleSize int
HoleSize int
GapWidth *uint
Overlap *int
order int
}
// ChartLegend directly maps the format settings of the chart legend.
type ChartLegend struct {
Position string
ShowLegendKey bool
Font *Font
}
// ChartMarker directly maps the format settings of the chart marker.
type ChartMarker struct {
Border ChartLine
Fill Fill
Symbol string
Size int
}
// ChartLine directly maps the format settings of the chart line.
type ChartLine struct {
Type ChartLineType
Dash ChartDashType
Fill Fill
Smooth bool
Width float64
}
// ChartDataLabel directly maps the format settings of the chart labels.
type ChartDataLabel struct {
Alignment Alignment
Font Font
Fill Fill
}
// ChartDataPoint directly maps the format settings of the chart data point for
// doughnut, pie and 3D pie charts.
type ChartDataPoint struct {
Index int
Fill Fill
}
// ChartSeries directly maps the format settings of the chart series.
type ChartSeries struct {
Name string
Categories string
Values string
Sizes string
Fill Fill
Legend ChartLegend
Line ChartLine
Marker ChartMarker
DataLabel ChartDataLabel
DataLabelPosition ChartDataLabelPositionType
DataPoint []ChartDataPoint
}
================================================
FILE: xmlChartSheet.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// struct code generated by github.com/xuri/xgen
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in
// a SpreadsheetML document.
type xlsxChartsheet struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"`
SheetPr *xlsxChartsheetPr `xml:"sheetPr"`
SheetViews *xlsxChartsheetViews `xml:"sheetViews"`
SheetProtection *xlsxChartsheetProtection `xml:"sheetProtection"`
CustomSheetViews *xlsxCustomChartsheetViews `xml:"customSheetViews"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PageSetup *xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
Drawing *xlsxDrawing `xml:"drawing"`
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
Picture *xlsxPicture `xml:"picture"`
WebPublishItems *xlsxInnerXML `xml:"webPublishItems"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetPr specifies chart sheet properties.
type xlsxChartsheetPr struct {
XMLName xml.Name `xml:"sheetPr"`
PublishedAttr bool `xml:"published,attr,omitempty"`
CodeNameAttr string `xml:"codeName,attr,omitempty"`
TabColor *xlsxColor `xml:"tabColor"`
}
// xlsxChartsheetViews specifies chart sheet views.
type xlsxChartsheetViews struct {
XMLName xml.Name `xml:"sheetViews"`
SheetView []*xlsxChartsheetView `xml:"sheetView"`
ExtLst []*xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetView defines custom view properties for chart sheets.
type xlsxChartsheetView struct {
XMLName xml.Name `xml:"sheetView"`
TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"`
ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"`
WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"`
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
ExtLst []*xlsxExtLst `xml:"extLst"`
}
// xlsxChartsheetProtection collection expresses the chart sheet protection
// options to enforce when the chart sheet is protected.
type xlsxChartsheetProtection struct {
XMLName xml.Name `xml:"sheetProtection"`
AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"`
HashValueAttr []byte `xml:"hashValue,attr,omitempty"`
SaltValueAttr []byte `xml:"saltValue,attr,omitempty"`
SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"`
ContentAttr bool `xml:"content,attr,omitempty"`
ObjectsAttr bool `xml:"objects,attr,omitempty"`
}
// xlsxCustomChartsheetViews collection of custom Chart Sheet View
// information.
type xlsxCustomChartsheetViews struct {
XMLName xml.Name `xml:"customSheetViews"`
CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"`
}
// xlsxCustomChartsheetView defines custom view properties for chart sheets.
type xlsxCustomChartsheetView struct {
XMLName xml.Name `xml:"customSheetView"`
GUIDAttr string `xml:"guid,attr"`
ScaleAttr uint32 `xml:"scale,attr,omitempty"`
StateAttr string `xml:"state,attr,omitempty"`
ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
PageMargins []*xlsxPageMargins `xml:"pageMargins"`
PageSetup []*xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"`
}
================================================
FILE: xmlComments.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxComments directly maps the comments element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. A comment is a
// rich text note that is attached to and associated with a cell, separate from
// other cell content. Comment content is stored separate from the cell, and is
// displayed in a drawing object (like a text box) that is separate from, but
// associated with, a cell. Comments are used as reminders, such as noting how a
// complex formula works, or to provide feedback to other users. Comments can
// also be used to explain assumptions made in a formula or to call out
// something special about the cell.
type xlsxComments struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"`
Authors xlsxAuthor `xml:"authors"`
CommentList xlsxCommentList `xml:"commentList"`
cells []string
}
// xlsxAuthor directly maps the author element. This element holds a string
// representing the name of a single author of comments. Every comment shall
// have an author. The maximum length of the author string is an implementation
// detail, but a good guideline is 255 chars.
type xlsxAuthor struct {
Author []string `xml:"author"`
}
// xlsxCommentList (List of Comments) directly maps the xlsxCommentList element.
// This element is a container that holds a list of comments for the sheet.
type xlsxCommentList struct {
Comment []xlsxComment `xml:"comment"`
}
// xlsxComment directly maps the comment element. This element represents a
// single user entered comment. Each comment shall have an author and can
// optionally contain richly formatted text.
type xlsxComment struct {
Ref string `xml:"ref,attr"`
AuthorID int `xml:"authorId,attr"`
Text xlsxText `xml:"text"`
}
// xlsxText directly maps the text element. This element contains rich text
// which represents the text of a comment. The maximum length for this text is a
// spreadsheet application implementation detail. A recommended guideline is
// 32767 chars.
type xlsxText struct {
T *string `xml:"t"`
R []xlsxR `xml:"r"`
RPh *xlsxPhoneticRun `xml:"rPh"`
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
}
// xlsxPhoneticRun element represents a run of text which displays a phonetic
// hint for this String Item (si). Phonetic hints are used to give information
// about the pronunciation of an East Asian language. The hints are displayed
// as text within the spreadsheet cells across the top portion of the cell.
type xlsxPhoneticRun struct {
Sb uint32 `xml:"sb,attr"`
Eb uint32 `xml:"eb,attr"`
T string `xml:"t"`
}
// Comment directly maps the comment information.
type Comment struct {
Author string
AuthorID int
Cell string
Text string
Width uint
Height uint
Paragraph []RichTextRun
}
================================================
FILE: xmlContentTypes.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxTypes directly maps the types' element of content types for relationship
// parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a
// value.
type xlsxTypes struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"`
Defaults []xlsxDefault `xml:"Default"`
Overrides []xlsxOverride `xml:"Override"`
}
// xlsxOverride directly maps the override element in the namespace
// http://schemas.openxmlformats.org/package/2006/content-types
type xlsxOverride struct {
PartName string `xml:",attr"`
ContentType string `xml:",attr"`
}
// xlsxDefault directly maps the default element in the namespace
// http://schemas.openxmlformats.org/package/2006/content-types
type xlsxDefault struct {
Extension string `xml:",attr"`
ContentType string `xml:",attr"`
}
================================================
FILE: xmlCore.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// DocProperties directly maps the document core properties.
type DocProperties struct {
Category string
ContentStatus string
Created string
Creator string
Description string
Identifier string
Keywords string
LastModifiedBy string
Modified string
Revision string
Subject string
Title string
Language string
Version string
}
// decodeDcTerms directly maps the DCMI metadata terms for the coreProperties.
type decodeDcTerms struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
}
// decodeCoreProperties directly maps the root element for a part of this
// content type shall coreProperties. In order to solve the problem that the
// label structure is changed after serialization and deserialization, two
// different structures are defined. decodeCoreProperties just for
// deserialization.
type decodeCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created *decodeDcTerms `xml:"http://purl.org/dc/terms/ created"`
Modified *decodeDcTerms `xml:"http://purl.org/dc/terms/ modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}
// xlsxDcTerms directly maps the DCMI metadata terms for the coreProperties.
type xlsxDcTerms struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
}
// xlsxCoreProperties directly maps the root element for a part of this
// content type shall coreProperties.
type xlsxCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Dc string `xml:"xmlns:dc,attr"`
Dcterms string `xml:"xmlns:dcterms,attr"`
Dcmitype string `xml:"xmlns:dcmitype,attr"`
XSI string `xml:"xmlns:xsi,attr"`
Title string `xml:"dc:title,omitempty"`
Subject string `xml:"dc:subject,omitempty"`
Creator string `xml:"dc:creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"dc:description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"dc:language,omitempty"`
Identifier string `xml:"dc:identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created *xlsxDcTerms `xml:"dcterms:created"`
Modified *xlsxDcTerms `xml:"dcterms:modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}
================================================
FILE: xmlCustom.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxCustomProperties directly maps the element for the custom file properties
// part, that represents additional information. The information can be used as
// metadata for XML.
type xlsxCustomProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties Properties"`
Vt string `xml:"xmlns:vt,attr"`
Property []xlsxProperty `xml:"property"`
}
// xlsxProperty directly maps the element specifies a single custom file
// property. Custom file property type is defined through child elements in the
// File Properties Variant Type namespace. Custom file property value can be set
// by setting the appropriate Variant Type child element value.
type xlsxProperty struct {
XMLName xml.Name `xml:"property"`
FmtID string `xml:"fmtid,attr"`
PID int `xml:"pid,attr"`
Name string `xml:"name,attr,omitempty"`
LinkTarget string `xml:"linkTarget,attr,omitempty"`
Vector *string `xml:"vt:vector"`
Array *string `xml:"vt:array"`
Blob *string `xml:"vt:blob"`
Oblob *string `xml:"vt:oblob"`
Empty *string `xml:"vt:empty"`
Null *string `xml:"vt:null"`
I1 *int8 `xml:"vt:i1"`
I2 *int16 `xml:"vt:i2"`
I4 *int32 `xml:"vt:i4"`
I8 *int64 `xml:"vt:i8"`
Int *int `xml:"vt:int"`
Ui1 *uint8 `xml:"vt:ui1"`
Ui2 *uint16 `xml:"vt:ui2"`
Ui4 *uint32 `xml:"vt:ui4"`
Ui8 *uint64 `xml:"vt:ui8"`
Uint *uint `xml:"vt:uint"`
R4 *float32 `xml:"vt:r4"`
R8 *float64 `xml:"vt:r8"`
Decimal *string `xml:"vt:decimal"`
Lpstr *string `xml:"vt:lpstr"`
Lpwstr *string `xml:"vt:lpwstr"`
Bstr *string `xml:"vt:bstr"`
Date *string `xml:"vt:date"`
FileTime *string `xml:"vt:filetime"`
Bool *bool `xml:"vt:bool"`
Cy *string `xml:"vt:cy"`
Error *string `xml:"vt:error"`
Stream *string `xml:"vt:stream"`
Ostream *string `xml:"vt:ostream"`
Storage *string `xml:"vt:storage"`
Ostorage *string `xml:"vt:ostorage"`
Vstream *string `xml:"vt:vstream"`
ClsID *string `xml:"vt:clsid"`
}
// decodeCustomProperties specifies to an OOXML document custom properties.
// decodeCustomProperties just for deserialization.
type decodeCustomProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties Properties"`
Vt string `xml:"xmlns:vt,attr"`
Property []decodeProperty `xml:"property"`
}
// decodeProperty specifies to an OOXML document custom property. decodeProperty
// just for deserialization.
type decodeProperty struct {
XMLName xml.Name `xml:"property"`
FmtID string `xml:"fmtid,attr"`
PID int `xml:"pid,attr"`
Name string `xml:"name,attr,omitempty"`
LinkTarget string `xml:"linkTarget,attr,omitempty"`
Vector *string `xml:"vector"`
Array *string `xml:"array"`
Blob *string `xml:"blob"`
Oblob *string `xml:"oblob"`
Empty *string `xml:"empty"`
Null *string `xml:"null"`
I1 *int8 `xml:"i1"`
I2 *int16 `xml:"i2"`
I4 *int32 `xml:"i4"`
I8 *int64 `xml:"i8"`
Int *int `xml:"int"`
Ui1 *uint8 `xml:"ui1"`
Ui2 *uint16 `xml:"ui2"`
Ui4 *uint32 `xml:"ui4"`
Ui8 *uint64 `xml:"ui8"`
Uint *uint `xml:"uint"`
R4 *float32 `xml:"r4"`
R8 *float64 `xml:"r8"`
Decimal *string `xml:"decimal"`
Lpstr *string `xml:"lpstr"`
Lpwstr *string `xml:"lpwstr"`
Bstr *string `xml:"bstr"`
Date *string `xml:"date"`
FileTime *string `xml:"filetime"`
Bool *bool `xml:"bool"`
Cy *string `xml:"cy"`
Error *string `xml:"error"`
Stream *string `xml:"stream"`
Ostream *string `xml:"ostream"`
Storage *string `xml:"storage"`
Ostorage *string `xml:"ostorage"`
Vstream *string `xml:"vstream"`
ClsID *string `xml:"clsid"`
}
// CustomProperty directly maps the custom property of the workbook. The value
// date type may be one of the following: int32, float64, string, bool,
// time.Time, or nil.
type CustomProperty struct {
Name string
Value interface{}
}
================================================
FILE: xmlDecodeDrawing.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
// element. It moves with cells and its extents are in EMU units.
type decodeCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Ext *decodePositiveSize2D `xml:"ext"`
Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
Content string `xml:",innerxml"`
}
// decodeCellAnchorPos defines the structure used to deserialize the cell anchor
// for adjust drawing object on inserting/deleting column/rows.
type decodeCellAnchorPos struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *xlsxFrom `xml:"from"`
To *xlsxTo `xml:"to"`
Pos *xlsxInnerXML `xml:"pos"`
Ext *xlsxPositiveSize2D `xml:"ext"`
Sp *xlsxSp `xml:"sp"`
GrpSp *xlsxInnerXML `xml:"grpSp"`
GraphicFrame *xlsxInnerXML `xml:"graphicFrame"`
CxnSp *xlsxInnerXML `xml:"cxnSp"`
Pic *xlsxInnerXML `xml:"pic"`
ContentPart *xlsxInnerXML `xml:"contentPart"`
AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
ClientData *xlsxInnerXML `xml:"clientData"`
}
// decodeChoice defines the structure used to deserialize the mc:Choice element.
type decodeChoice struct {
XMLName xml.Name `xml:"Choice"`
XMLNSA14 string `xml:"a14,attr"`
XMLNSSle15 string `xml:"sle15,attr"`
Requires string `xml:"Requires,attr"`
GraphicFrame decodeGraphicFrame `xml:"graphicFrame"`
}
// decodeGraphicFrame defines the structure used to deserialize the
// xdr:graphicFrame element.
type decodeGraphicFrame struct {
Macro string `xml:"macro,attr"`
NvGraphicFramePr decodeNvGraphicFramePr `xml:"nvGraphicFramePr"`
}
// decodeNvGraphicFramePr defines the structure used to deserialize the
// xdr:nvGraphicFramePr element.
type decodeNvGraphicFramePr struct {
CNvPr decodeCNvPr `xml:"cNvPr"`
}
// decodeSp defines the structure used to deserialize the sp element.
type decodeSp struct {
Macro string `xml:"macro,attr,omitempty"`
TextLink string `xml:"textlink,attr,omitempty"`
FLocksText bool `xml:"fLocksText,attr,omitempty"`
FPublished *bool `xml:"fPublished,attr"`
NvSpPr *decodeNvSpPr `xml:"nvSpPr"`
SpPr *decodeSpPr `xml:"spPr"`
}
// decodeNvSpPr (Non-Visual Properties for a Shape) directly maps the nvSpPr
// element. This element specifies all non-visual properties for a shape. This
// element is a container for the non-visual identification properties, shape
// properties and application properties that are to be associated with a
// shape. This allows for additional information that does not affect the
// appearance of the shape to be stored.
type decodeNvSpPr struct {
CNvPr *decodeCNvPr `xml:"cNvPr"`
ExtLst *decodePositiveSize2D `xml:"extLst"`
CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"`
}
// decodeCNvSpPr (Connection Non-Visual Shape Properties) directly maps the
// cNvSpPr element. This element specifies the set of non-visual properties
// for a connection shape. These properties specify all data about the
// connection shape which do not affect its display within a spreadsheet.
type decodeCNvSpPr struct {
TxBox bool `xml:"txBox,attr"`
}
// decodeWsDr directly maps the root element for a part of this content type
// shall wsDr. In order to solve the problem that the label structure is
// changed after serialization and deserialization, two different structures
// are defined. decodeWsDr just for deserialization.
type decodeWsDr struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr"`
A string `xml:"xmlns a,attr"`
Xdr string `xml:"xmlns xdr,attr"`
R string `xml:"xmlns r,attr"`
AlternateContent []*xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"`
TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"`
}
// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be
// stored.
type decodeCNvPr struct {
XMLName xml.Name `xml:"cNvPr"`
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
Descr string `xml:"descr,attr"`
Title string `xml:"title,attr,omitempty"`
HlinkClick *decodeHlinkClick `xml:"hlinkClick,omitempty"`
}
// decodeHlinkClick directly maps the hlinkClick (Hyperlink Click).This element
// specifies the on-click hyperlink information to be applied to a run of text.
// When the hyperlink text is clicked the link is fetched.
// clicked the link is fetched.
type decodeHlinkClick struct {
RID string `xml:"id,attr,omitempty"`
InvalidURL string `xml:"invalidUrl,attr,omitempty"`
Action string `xml:"action,attr,omitempty"`
TgtFrame string `xml:"tgtFrame,attr,omitempty"`
Tooltip string `xml:"tooltip,attr,omitempty"`
History bool `xml:"history,attr,omitempty"`
HighlightClick bool `xml:"highlightClick,attr,omitempty"`
EndSnd bool `xml:"endSnd,attr,omitempty"`
}
// decodePicLocks directly maps the picLocks (Picture Locks). This element
// specifies all locking properties for a graphic frame. These properties
// inform the generating application about specific properties that have been
// previously locked and thus should not be changed.
type decodePicLocks struct {
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"`
NoChangeAspect bool `xml:"noChangeAspect,attr"`
NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"`
NoCrop bool `xml:"noCrop,attr,omitempty"`
NoEditPoints bool `xml:"noEditPoints,attr,omitempty"`
NoGrp bool `xml:"noGrp,attr,omitempty"`
NoMove bool `xml:"noMove,attr,omitempty"`
NoResize bool `xml:"noResize,attr,omitempty"`
NoRot bool `xml:"noRot,attr,omitempty"`
NoSelect bool `xml:"noSelect,attr,omitempty"`
}
// decodeBlip element specifies the existence of an image (binary large image
// or picture) and contains a reference to the image data.
type decodeBlip struct {
Embed string `xml:"embed,attr"`
Cstate string `xml:"cstate,attr,omitempty"`
R string `xml:"r,attr"`
}
// decodeStretch directly maps the stretch element. This element specifies
// that a BLIP should be stretched to fill the target rectangle. The other
// option is a tile where a BLIP is tiled to fill the available area.
type decodeStretch struct {
FillRect string `xml:"fillRect"`
}
// decodeOff directly maps the colOff and rowOff element. This element is used
// to specify the column offset within a cell.
type decodeOff struct {
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
// decodePositiveSize2D directly maps the a:ext element.
type decodePositiveSize2D struct {
Cx int `xml:"cx,attr"`
Cy int `xml:"cy,attr"`
}
// decodePrstGeom directly maps the prstGeom (Preset geometry). This element
// specifies when a preset geometric shape should be used instead of a custom
// geometric shape. The generating application should be able to render all
// preset geometries enumerated in the ST_ShapeType list.
type decodePrstGeom struct {
Prst string `xml:"prst,attr"`
}
// decodeXfrm directly maps the xfrm (2D Transform for Graphic Frame). This
// element specifies the transform to be applied to the corresponding graphic
// frame. This transformation is applied to the graphic frame just as it would
// be for a shape or group shape.
type decodeXfrm struct {
Off decodeOff `xml:"off"`
Ext decodePositiveSize2D `xml:"ext"`
}
// decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing
// Properties). This element specifies the non-visual properties for the picture
// canvas. These properties are to be used by the generating application to
// determine how certain properties are to be changed for the picture object in
// question.
type decodeCNvPicPr struct {
PicLocks decodePicLocks `xml:"picLocks"`
}
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This
// element specifies all non-visual properties for a picture. This element is
// a container for the non-visual identification properties, shape properties
// and application properties that are to be associated with a picture. This
// allows for additional information that does not affect the appearance of
// the picture to be stored.
type decodeNvPicPr struct {
CNvPr decodeCNvPr `xml:"cNvPr"`
CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"`
}
// decodeBlipFill directly maps the blipFill (Picture Fill). This element
// specifies the kind of picture fill that the picture object has. Because a
// picture has a picture fill already by default, it is possible to have two
// fills specified for a picture object.
type decodeBlipFill struct {
Blip decodeBlip `xml:"blip"`
Stretch decodeStretch `xml:"stretch"`
}
// decodeSpPr directly maps the spPr (Shape Properties). This element
// specifies the visual shape properties that can be applied to a picture.
// These are the same properties that are allowed to describe the visual
// properties of a shape but are used here to describe the visual appearance
// of a picture within a document.
type decodeSpPr struct {
Xfrm decodeXfrm `xml:"xfrm"`
PrstGeom decodePrstGeom `xml:"prstGeom"`
}
// decodePic elements encompass the definition of pictures within the
// DrawingML framework. While pictures are in many ways very similar to shapes
// they have specific properties that are unique in order to optimize for
// picture-specific scenarios.
type decodePic struct {
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
BlipFill decodeBlipFill `xml:"blipFill"`
SpPr decodeSpPr `xml:"spPr"`
}
// decodeFrom specifies the starting anchor.
type decodeFrom struct {
Col int `xml:"col"`
ColOff int `xml:"colOff"`
Row int `xml:"row"`
RowOff int `xml:"rowOff"`
}
// decodeTo directly specifies the ending anchor.
type decodeTo struct {
Col int `xml:"col"`
ColOff int `xml:"colOff"`
Row int `xml:"row"`
RowOff int `xml:"rowOff"`
}
// decodeClientData directly maps the clientData element. An empty element
// which specifies (via attributes) certain properties related to printing and
// selection of the drawing object. The fLocksWithSheet attribute (either true
// or false) determines whether to disable selection when the sheet is
// protected, and fPrintsWithSheet attribute (either true or false) determines
// whether the object is printed when the sheet is printed.
type decodeClientData struct {
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
}
// decodeCellImages directly maps the Kingsoft WPS Office embedded cell images.
type decodeCellImages struct {
XMLName xml.Name `xml:"http://www.wps.cn/officeDocument/2017/etCustomData cellImages"`
CellImage []decodeCellImage `xml:"cellImage"`
}
// decodeCellImage defines the structure used to deserialize the Kingsoft WPS
// Office embedded cell images.
type decodeCellImage struct {
Pic decodePic `xml:"pic"`
}
================================================
FILE: xmlDrawing.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be stored.
type xlsxCNvPr struct {
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
Descr string `xml:"descr,attr"`
Title string `xml:"title,attr,omitempty"`
HlinkClick *xlsxHlinkClick `xml:"a:hlinkClick"`
}
// xlsxHlinkClick (Click Hyperlink) Specifies the on-click hyperlink
// information to be applied to a run of text. When the hyperlink text is
// clicked the link is fetched.
type xlsxHlinkClick struct {
R string `xml:"xmlns:r,attr,omitempty"`
RID string `xml:"r:id,attr,omitempty"`
InvalidURL string `xml:"invalidUrl,attr,omitempty"`
Action string `xml:"action,attr,omitempty"`
TgtFrame string `xml:"tgtFrame,attr,omitempty"`
Tooltip string `xml:"tooltip,attr,omitempty"`
History bool `xml:"history,attr,omitempty"`
HighlightClick bool `xml:"highlightClick,attr,omitempty"`
EndSnd bool `xml:"endSnd,attr,omitempty"`
}
// xlsxPicLocks directly maps the picLocks (Picture Locks). This element
// specifies all locking properties for a graphic frame. These properties inform
// the generating application about specific properties that have been
// previously locked and thus should not be changed.
type xlsxPicLocks struct {
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"`
NoChangeAspect bool `xml:"noChangeAspect,attr"`
NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"`
NoCrop bool `xml:"noCrop,attr,omitempty"`
NoEditPoints bool `xml:"noEditPoints,attr,omitempty"`
NoGrp bool `xml:"noGrp,attr,omitempty"`
NoMove bool `xml:"noMove,attr,omitempty"`
NoResize bool `xml:"noResize,attr,omitempty"`
NoRot bool `xml:"noRot,attr,omitempty"`
NoSelect bool `xml:"noSelect,attr,omitempty"`
}
// xlsxBlip element specifies the existence of an image (binary large image or
// picture) and contains a reference to the image data.
type xlsxBlip struct {
Embed string `xml:"r:embed,attr"`
Cstate string `xml:"cstate,attr,omitempty"`
R string `xml:"xmlns:r,attr"`
ExtList *xlsxEGOfficeArtExtensionList `xml:"a:extLst"`
}
// xlsxStretch directly maps the stretch element. This element specifies that a
// BLIP should be stretched to fill the target rectangle. The other option is a
// tile where a BLIP is tiled to fill the available area.
type xlsxStretch struct {
FillRect string `xml:"a:fillRect"`
}
// xlsxOff directly maps the colOff and rowOff element. This element is used to
// specify the column offset within a cell.
type xlsxOff struct {
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
// xlsxPositiveSize2D directly maps the a:ext element.
type xlsxPositiveSize2D struct {
Cx int `xml:"cx,attr"`
Cy int `xml:"cy,attr"`
}
// xlsxPrstGeom directly maps the prstGeom (Preset geometry). This element
// specifies when a preset geometric shape should be used instead of a custom
// geometric shape. The generating application should be able to render all
// preset geometries enumerated in the ST_ShapeType list.
type xlsxPrstGeom struct {
Prst string `xml:"prst,attr"`
}
// xlsxXfrm directly maps the xfrm (2D Transform for Graphic Frame). This
// element specifies the transform to be applied to the corresponding graphic
// frame. This transformation is applied to the graphic frame just as it would
// be for a shape or group shape.
type xlsxXfrm struct {
Off xlsxOff `xml:"a:off"`
Ext xlsxPositiveSize2D `xml:"a:ext"`
}
// xlsxCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing
// Properties). This element specifies the non-visual properties for the picture
// canvas. These properties are to be used by the generating application to
// determine how certain properties are to be changed for the picture object in
// question.
type xlsxCNvPicPr struct {
PicLocks xlsxPicLocks `xml:"a:picLocks"`
}
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This element
// specifies all non-visual properties for a picture. This element is a
// container for the non-visual identification properties, shape properties and
// application properties that are to be associated with a picture. This allows
// for additional information that does not affect the appearance of the picture
// to be stored.
type xlsxNvPicPr struct {
CNvPr xlsxCNvPr `xml:"xdr:cNvPr"`
CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"`
}
// xlsxCTSVGBlip specifies a graphic element in Scalable Vector Graphics (SVG)
// format.
type xlsxCTSVGBlip struct {
XMLNSaAVG string `xml:"xmlns:asvg,attr"`
Embed string `xml:"r:embed,attr"`
Link string `xml:"r:link,attr,omitempty"`
}
// xlsxCTOfficeArtExtension used for future extensibility and is seen elsewhere
// throughout the drawing area.
type xlsxCTOfficeArtExtension struct {
XMLName xml.Name `xml:"a:ext"`
URI string `xml:"uri,attr"`
SVGBlip xlsxCTSVGBlip `xml:"asvg:svgBlip"`
}
// xlsxEGOfficeArtExtensionList used for future extensibility and is seen
// elsewhere throughout the drawing area.
type xlsxEGOfficeArtExtensionList struct {
Ext []xlsxCTOfficeArtExtension `xml:"ext"`
}
// xlsxBlipFill directly maps the blipFill (Picture Fill). This element
// specifies the kind of picture fill that the picture object has. Because a
// picture has a picture fill already by default, it is possible to have two
// fills specified for a picture object.
type xlsxBlipFill struct {
Blip xlsxBlip `xml:"a:blip"`
Stretch xlsxStretch `xml:"a:stretch"`
}
// xlsxLineProperties specifies the width of a line in EMUs. This simple type
// has a minimum value of greater than or equal to 0. This simple type has a
// maximum value of less than or equal to 20116800.
type xlsxLineProperties struct {
W int `xml:"w,attr,omitempty"`
SolidFill *xlsxInnerXML `xml:"a:solidFill"`
}
// xlsxSpPr directly maps the spPr (Shape Properties). This element specifies
// the visual shape properties that can be applied to a picture. These are the
// same properties that are allowed to describe the visual properties of a shape
// but are used here to describe the visual appearance of a picture within a
// document.
type xlsxSpPr struct {
Xfrm xlsxXfrm `xml:"a:xfrm"`
PrstGeom xlsxPrstGeom `xml:"a:prstGeom"`
SolidFill *aSolidFill `xml:"a:solidFill"`
Ln xlsxLineProperties `xml:"a:ln"`
}
// xlsxPic elements encompass the definition of pictures within the DrawingML
// framework. While pictures are in many ways very similar to shapes they have
// specific properties that are unique in order to optimize for picture-
// specific scenarios.
type xlsxPic struct {
NvPicPr xlsxNvPicPr `xml:"xdr:nvPicPr"`
BlipFill xlsxBlipFill `xml:"xdr:blipFill"`
SpPr xlsxSpPr `xml:"xdr:spPr"`
}
// xlsxFrom specifies the starting anchor.
type xlsxFrom struct {
Col int `xml:"xdr:col"`
ColOff int `xml:"xdr:colOff"`
Row int `xml:"xdr:row"`
RowOff int `xml:"xdr:rowOff"`
}
// xlsxTo directly specifies the ending anchor.
type xlsxTo struct {
Col int `xml:"xdr:col"`
ColOff int `xml:"xdr:colOff"`
Row int `xml:"xdr:row"`
RowOff int `xml:"xdr:rowOff"`
}
// xdrClientData directly maps the clientData element. An empty element which
// specifies (via attributes) certain properties related to printing and
// selection of the drawing object. The fLocksWithSheet attribute (either true
// or false) determines whether to disable selection when the sheet is
// protected, and fPrintsWithSheet attribute (either true or false) determines
// whether the object is printed when the sheet is printed.
type xdrClientData struct {
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
}
// xdrCellAnchor specifies a oneCellAnchor (One Cell Anchor Shape Size) and
// twoCellAnchor (Two Cell Anchor Shape Size) placeholder for a group, a shape,
// or a drawing element. It moves with cells and its extents are in EMU units.
type xdrCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"`
Pos *xlsxPoint2D `xml:"xdr:pos"`
From *xlsxFrom `xml:"xdr:from"`
To *xlsxTo `xml:"xdr:to"`
Ext *xlsxPositiveSize2D `xml:"xdr:ext"`
Sp *xdrSp `xml:"xdr:sp"`
Pic *xlsxPic `xml:"xdr:pic,omitempty"`
GraphicFrame string `xml:",innerxml"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
ClientData *xdrClientData `xml:"xdr:clientData"`
}
// xlsxCellAnchorPos defines the structure used to serialize the cell anchor for
// adjust drawing object on inserting/deleting column/rows.
type xlsxCellAnchorPos struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *xlsxFrom `xml:"xdr:from"`
To *xlsxTo `xml:"xdr:to"`
Pos *xlsxInnerXML `xml:"xdr:pos"`
Ext *xlsxPositiveSize2D `xml:"xdr:ext"`
Sp *xlsxSp `xml:"xdr:sp"`
GrpSp *xlsxInnerXML `xml:"xdr:grpSp"`
GraphicFrame *xlsxInnerXML `xml:"xdr:graphicFrame"`
CxnSp *xlsxInnerXML `xml:"xdr:cxnSp"`
Pic *xlsxInnerXML `xml:"xdr:pic"`
ContentPart *xlsxInnerXML `xml:"xdr:contentPart"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
ClientData *xlsxInnerXML `xml:"xdr:clientData"`
}
// xdrSp (Shape) directly maps the sp element. This element specifies the
// existence of a single shape. A shape can either be a preset or a custom
// geometry, defined using the SpreadsheetDrawingML framework. In addition to
// a geometry each shape can have both visual and non-visual properties
// attached. Text and corresponding styling information can also be attached
// to a shape. This shape is specified along with all other shapes within
// either the shape tree or group shape elements.
type xlsxSp struct {
Macro string `xml:"macro,attr,omitempty"`
TextLink string `xml:"textlink,attr,omitempty"`
FLocksText bool `xml:"fLocksText,attr,omitempty"`
FPublished *bool `xml:"fPublished,attr"`
Content string `xml:",innerxml"`
}
// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
type xlsxPoint2D struct {
XMLName xml.Name `xml:"xdr:pos"`
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
// xlsxWsDr directly maps the root element for a part of this content type shall
// wsDr.
type xlsxWsDr struct {
mu sync.Mutex
XMLName xml.Name `xml:"xdr:wsDr"`
NS string `xml:"xmlns,attr,omitempty"`
A string `xml:"xmlns:a,attr,omitempty"`
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
R string `xml:"xmlns:r,attr,omitempty"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"`
OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
}
// xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element.
// This element specifies the existence of a graphics frame. This frame contains
// a graphic that was generated by an external source and needs a container in
// which to be displayed on the slide surface.
type xlsxGraphicFrame struct {
XMLName xml.Name `xml:"xdr:graphicFrame"`
Macro string `xml:"macro,attr"`
NvGraphicFramePr xlsxNvGraphicFramePr `xml:"xdr:nvGraphicFramePr"`
Xfrm xlsxXfrm `xml:"xdr:xfrm"`
Graphic *xlsxGraphic `xml:"a:graphic"`
}
// xlsxNvGraphicFramePr (Non-Visual Properties for a Graphic Frame) directly
// maps the xdr:nvGraphicFramePr element. This element specifies all non-visual
// properties for a graphic frame. This element is a container for the non-
// visual identification properties, shape properties and application properties
// that are to be associated with a graphic frame. This allows for additional
// information that does not affect the appearance of the graphic frame to be
// stored.
type xlsxNvGraphicFramePr struct {
CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"`
ChicNvGraphicFramePr string `xml:"xdr:cNvGraphicFramePr"`
}
// xlsxGraphic (Graphic Object) directly maps the a:graphic element. This
// element specifies the existence of a single graphic object. Document authors
// should refer to this element when they wish to persist a graphical object of
// some kind. The specification for this graphical object is provided entirely
// by the document author and referenced within the graphicData child element.
type xlsxGraphic struct {
GraphicData *xlsxGraphicData `xml:"a:graphicData"`
}
// xlsxGraphicData (Graphic Object Data) directly maps the a:graphicData
// element. This element specifies the reference to a graphic object within the
// document. This graphic object is provided entirely by the document authors
// who choose to persist this data within the document.
type xlsxGraphicData struct {
URI string `xml:"uri,attr"`
Chart *xlsxChart `xml:"c:chart,omitempty"`
Sle *xlsxSle `xml:"sle:slicer"`
}
type xlsxSle struct {
XMLNS string `xml:"xmlns:sle,attr"`
Name string `xml:"name,attr"`
}
// xlsxChart (Chart) directly maps the c:chart element.
type xlsxChart struct {
C string `xml:"xmlns:c,attr"`
RID string `xml:"r:id,attr"`
R string `xml:"xmlns:r,attr"`
}
// xdrSp (Shape) directly maps the xdr:sp element. This element specifies the
// existence of a single shape. A shape can either be a preset or a custom
// geometry, defined using the SpreadsheetDrawingML framework. In addition to a
// geometry each shape can have both visual and non-visual properties attached.
// Text and corresponding styling information can also be attached to a shape.
// This shape is specified along with all other shapes within either the shape
// tree or group shape elements.
type xdrSp struct {
XMLName xml.Name `xml:"xdr:sp"`
Macro string `xml:"macro,attr"`
Textlink string `xml:"textlink,attr"`
NvSpPr *xdrNvSpPr `xml:"xdr:nvSpPr"`
SpPr *xlsxSpPr `xml:"xdr:spPr"`
Style *xdrStyle `xml:"xdr:style"`
TxBody *xdrTxBody `xml:"xdr:txBody"`
}
// xdrNvSpPr (Non-Visual Properties for a Shape) directly maps the xdr:nvSpPr
// element. This element specifies all non-visual properties for a shape. This
// element is a container for the non-visual identification properties, shape
// properties and application properties that are to be associated with a shape.
// This allows for additional information that does not affect the appearance of
// the shape to be stored.
type xdrNvSpPr struct {
CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"`
CNvSpPr *xdrCNvSpPr `xml:"xdr:cNvSpPr"`
}
// xdrCNvSpPr (Connection Non-Visual Shape Properties) directly maps the
// xdr:cNvSpPr element. This element specifies the set of non-visual properties
// for a connection shape. These properties specify all data about the
// connection shape which do not affect its display within a spreadsheet.
type xdrCNvSpPr struct {
TxBox bool `xml:"txBox,attr"`
}
// xdrStyle (Shape Style) directly maps the xdr:style element. The element
// specifies the style that is applied to a shape and the corresponding
// references for each of the style components such as lines and fills.
type xdrStyle struct {
LnRef *aRef `xml:"a:lnRef"`
FillRef *aRef `xml:"a:fillRef"`
EffectRef *aRef `xml:"a:effectRef"`
FontRef *aFontRef `xml:"a:fontRef"`
}
// aRef directly maps the a:lnRef, a:fillRef and a:effectRef element.
type aRef struct {
Idx int `xml:"idx,attr"`
ScrgbClr *aScrgbClr `xml:"a:scrgbClr"`
SchemeClr *attrValString `xml:"a:schemeClr"`
SrgbClr *attrValString `xml:"a:srgbClr"`
}
// aScrgbClr (RGB Color Model - Percentage Variant) directly maps the a:scrgbClr
// element. This element specifies a color using the red, green, blue RGB color
// model. Each component, red, green, and blue is expressed as a percentage from
// 0% to 100%. A linear gamma of 1.0 is assumed.
type aScrgbClr struct {
R float64 `xml:"r,attr"`
G float64 `xml:"g,attr"`
B float64 `xml:"b,attr"`
}
// aFontRef (Font Reference) directly maps the a:fontRef element. This element
// represents a reference to a themed font. When used it specifies which themed
// font to use along with a choice of color.
type aFontRef struct {
Idx string `xml:"idx,attr"`
SchemeClr *attrValString `xml:"a:schemeClr"`
}
// xdrTxBody (Shape Text Body) directly maps the xdr:txBody element. This
// element specifies the existence of text to be contained within the
// corresponding shape. All visible text and visible text related properties are
// contained within this element. There can be multiple paragraphs and within
// paragraphs multiple runs of text.
type xdrTxBody struct {
BodyPr *aBodyPr `xml:"a:bodyPr"`
P []*aP `xml:"a:p"`
}
// Picture maps the format settings of the picture.
type Picture struct {
Extension string
File []byte
Format *GraphicOptions
InsertType PictureInsertType
}
// GraphicOptions directly maps the format settings of the picture.
type GraphicOptions struct {
AltText string
Name string
PrintObject *bool
Locked *bool
LockAspectRatio bool
AutoFit bool
AutoFitIgnoreAspect bool
OffsetX int
OffsetY int
ScaleX float64
ScaleY float64
Hyperlink string
HyperlinkType string
Positioning string
}
// Shape directly maps the format settings of the shape.
type Shape struct {
Cell string
Type string
Macro string
Width uint
Height uint
Format GraphicOptions
Fill Fill
Line ShapeLine
Paragraph []RichTextRun
}
// ShapeLine directly maps the line settings of the shape.
type ShapeLine struct {
Color string
Width *float64
}
================================================
FILE: xmlMetaData.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxMetadata directly maps the metadata element. A cell in a spreadsheet
// application can have metadata associated with it. Metadata is just a set of
// additional properties about the particular cell, and this metadata is stored
// in the metadata xml part. There are two types of metadata: cell metadata and
// value metadata. Cell metadata contains information about the cell itself,
// and this metadata can be carried along with the cell as it moves
// (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is
// information about the value of a particular cell. Value metadata properties
// can be propagated along with the value as it is referenced in formulas.
type xlsxMetadata struct {
XMLName xml.Name `xml:"metadata"`
MetadataTypes *xlsxInnerXML `xml:"metadataTypes"`
MetadataStrings *xlsxInnerXML `xml:"metadataStrings"`
MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"`
FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"`
CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"`
ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxFutureMetadata directly maps the futureMetadata element. This element
// represents future metadata information.
type xlsxFutureMetadata struct {
Bk []xlsxFutureMetadataBlock `xml:"bk"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxFutureMetadataBlock directly maps the kb element. This element represents
// a block of future metadata information. This is a location for storing
// feature extension information.
type xlsxFutureMetadataBlock struct {
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxMetadataBlocks directly maps the metadata element. This element
// represents cell metadata information. Cell metadata is information metadata
// about a specific cell, and it stays tied to that cell position.
type xlsxMetadataBlocks struct {
Count int `xml:"count,attr,omitempty"`
Bk []xlsxMetadataBlock `xml:"bk"`
}
// xlsxMetadataBlock directly maps the bk element. This element represents a
// block of metadata records.
type xlsxMetadataBlock struct {
Rc []xlsxMetadataRecord `xml:"rc"`
}
// xlsxMetadataRecord directly maps the rc element. This element represents a
// reference to a specific metadata record.
type xlsxMetadataRecord struct {
T int `xml:"t,attr"`
V int `xml:"v,attr"`
}
// xlsxRichValueData directly maps the rvData element that specifies rich value
// data.
type xlsxRichValueData struct {
XMLName xml.Name `xml:"rvData"`
Count int `xml:"count,attr,omitempty"`
Rv []xlsxRichValue `xml:"rv"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxRichValue directly maps the rv element that specifies rich value data
// information for a single rich value
type xlsxRichValue struct {
S int `xml:"s,attr"`
V []string `xml:"v"`
Fb *xlsxInnerXML `xml:"fb"`
}
// xlsxRichValueRels directly maps the richValueRels element. This element that
// specifies a list of rich value relationships.
type xlsxRichValueRels struct {
XMLName xml.Name `xml:"richValueRels"`
Rels []xlsxRichValueRelRelationship `xml:"rel"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxRichValueRelRelationship directly maps the rel element. This element
// specifies a relationship for a rich value property.
type xlsxRichValueRelRelationship struct {
ID string `xml:"id,attr"`
}
// xlsxRichValueStructures directly maps the rvStructures element. This element
// specifies rich value structures, which contain lists of rich value keys and
// the data types for the corresponding rich value data.
type xlsxRichValueStructures struct {
XMLName xml.Name `xml:"rvStructures"`
Count int `xml:"count,attr,omitempty"`
S []xlsxRichValueStructure `xml:"s"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxRichValueStructure directly maps the s element. This element specifies
// the list of rich value structures.
type xlsxRichValueStructure struct {
T string `xml:"t,attr"`
K []xlsxRichValueKey `xml:"k"`
}
// xlsxRichValueKey directly maps the k element. This element specifies the rich
// value key.
type xlsxRichValueKey struct {
N string `xml:"n,attr"`
T string `xml:"t,attr,omitempty"`
}
// xlsxWebImagesSupportingRichData directly maps the webImagesSrd element. This
// element specifies a list of sets of properties associated with web image rich
// values.
type xlsxWebImagesSupportingRichData struct {
XMLName xml.Name `xml:"webImagesSrd"`
WebImageSrd []xlsxWebImageSupportingRichData `xml:"webImageSrd"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxWebImageSupportingRichData directly maps the webImageSrd element. This
// element specifies a set of properties for a web image rich value.
type xlsxWebImageSupportingRichData struct {
Address xlsxExternalReference `xml:"address"`
MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"`
Blip xlsxExternalReference `xml:"blip"`
}
================================================
FILE: xmlPivotCache.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part
// defines each field in the source data, including the name, the string
// resources of the instance data (for shared items), and information about
// the type of data that appears in the field.
type xlsxPivotCacheDefinition struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Invalid bool `xml:"invalid,attr,omitempty"`
SaveData bool `xml:"saveData,attr"`
RefreshOnLoad bool `xml:"refreshOnLoad,attr,omitempty"`
OptimizeMemory bool `xml:"optimizeMemory,attr,omitempty"`
EnableRefresh bool `xml:"enableRefresh,attr,omitempty"`
RefreshedBy string `xml:"refreshedBy,attr,omitempty"`
RefreshedDate float64 `xml:"refreshedDate,attr,omitempty"`
RefreshedDateIso float64 `xml:"refreshedDateIso,attr,omitempty"`
BackgroundQuery bool `xml:"backgroundQuery,attr"`
MissingItemsLimit int `xml:"missingItemsLimit,attr,omitempty"`
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
RefreshedVersion int `xml:"refreshedVersion,attr,omitempty"`
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
RecordCount int `xml:"recordCount,attr,omitempty"`
UpgradeOnRefresh bool `xml:"upgradeOnRefresh,attr,omitempty"`
TupleCacheAttr bool `xml:"tupleCache,attr,omitempty"`
SupportSubquery bool `xml:"supportSubquery,attr,omitempty"`
SupportAdvancedDrill bool `xml:"supportAdvancedDrill,attr,omitempty"`
CacheSource *xlsxCacheSource `xml:"cacheSource"`
CacheFields *xlsxCacheFields `xml:"cacheFields"`
CacheHierarchies *xlsxCacheHierarchies `xml:"cacheHierarchies"`
Kpis *xlsxKpis `xml:"kpis"`
TupleCache *xlsxTupleCache `xml:"tupleCache"`
CalculatedItems *xlsxCalculatedItems `xml:"calculatedItems"`
CalculatedMembers *xlsxCalculatedMembers `xml:"calculatedMembers"`
Dimensions *xlsxDimensions `xml:"dimensions"`
MeasureGroups *xlsxMeasureGroups `xml:"measureGroups"`
Maps *xlsxMaps `xml:"maps"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxCacheSource represents the description of data source whose data is
// stored in the pivot cache. The data source refers to the underlying rows or
// database records that provide the data for a PivotTable. You can create a
// PivotTable report from a SpreadsheetML table, an external database
// (including OLAP cubes), multiple SpreadsheetML worksheets, or another
// PivotTable.
type xlsxCacheSource struct {
Type string `xml:"type,attr"`
ConnectionID int `xml:"connectionId,attr,omitempty"`
WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"`
Consolidation *xlsxConsolidation `xml:"consolidation"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxWorksheetSource represents the location of the source of the data that
// is stored in the cache.
type xlsxWorksheetSource struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Ref string `xml:"ref,attr,omitempty"`
Name string `xml:"name,attr,omitempty"`
Sheet string `xml:"sheet,attr,omitempty"`
}
// xlsxConsolidation represents the description of the PivotCache source using
// multiple consolidation ranges. This element is used when the source of the
// PivotTable is a collection of ranges in the workbook. The ranges are
// specified in the rangeSets collection. The logic for how the application
// consolidates the data in the ranges is application- defined.
type xlsxConsolidation struct{}
// xlsxCacheFields represents the collection of field definitions in the
// source data.
type xlsxCacheFields struct {
Count int `xml:"count,attr"`
CacheField []*xlsxCacheField `xml:"cacheField"`
}
// xlsxCacheField represent a single field in the PivotCache. This definition
// contains information about the field, such as its source, data type, and
// location within a level or hierarchy. The sharedItems element stores
// additional information about the data in this field. If there are no shared
// items, then values are stored directly in the pivotCacheRecords part.
type xlsxCacheField struct {
Name string `xml:"name,attr"`
Caption string `xml:"caption,attr,omitempty"`
PropertyName string `xml:"propertyName,attr,omitempty"`
ServerField bool `xml:"serverField,attr,omitempty"`
UniqueList bool `xml:"uniqueList,attr,omitempty"`
NumFmtID int `xml:"numFmtId,attr"`
Formula string `xml:"formula,attr,omitempty"`
SQLType int `xml:"sqlType,attr,omitempty"`
Hierarchy int `xml:"hierarchy,attr,omitempty"`
Level int `xml:"level,attr,omitempty"`
DatabaseField bool `xml:"databaseField,attr,omitempty"`
MappingCount int `xml:"mappingCount,attr,omitempty"`
MemberPropertyField bool `xml:"memberPropertyField,attr,omitempty"`
SharedItems *xlsxSharedItems `xml:"sharedItems"`
FieldGroup *xlsxFieldGroup `xml:"fieldGroup"`
MpMap *xlsxX `xml:"mpMap"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxSharedItems represents the collection of unique items for a field in
// the PivotCacheDefinition. The sharedItems complex type stores data type and
// formatting information about the data in a field. Items in the
// PivotCacheDefinition can be shared in order to reduce the redundancy of
// those values that are referenced in multiple places across all the
// PivotTable parts.
type xlsxSharedItems struct {
ContainsSemiMixedTypes bool `xml:"containsSemiMixedTypes,attr,omitempty"`
ContainsNonDate bool `xml:"containsNonDate,attr,omitempty"`
ContainsDate bool `xml:"containsDate,attr,omitempty"`
ContainsString bool `xml:"containsString,attr,omitempty"`
ContainsBlank bool `xml:"containsBlank,attr,omitempty"`
ContainsMixedTypes bool `xml:"containsMixedTypes,attr,omitempty"`
ContainsNumber bool `xml:"containsNumber,attr,omitempty"`
ContainsInteger bool `xml:"containsInteger,attr,omitempty"`
MinValue float64 `xml:"minValue,attr,omitempty"`
MaxValue float64 `xml:"maxValue,attr,omitempty"`
MinDate string `xml:"minDate,attr,omitempty"`
MaxDate string `xml:"maxDate,attr,omitempty"`
Count int `xml:"count,attr"`
LongText bool `xml:"longText,attr,omitempty"`
M []xlsxMissing `xml:"m"`
N []xlsxNumber `xml:"n"`
B []xlsxBoolean `xml:"b"`
E []xlsxError `xml:"e"`
S []xlsxString `xml:"s"`
D []xlsxDateTime `xml:"d"`
}
// xlsxMissing represents a value that was not specified.
type xlsxMissing struct{}
// xlsxNumber represents a numeric value in the PivotTable.
type xlsxNumber struct {
V float64 `xml:"v,attr"`
U bool `xml:"u,attr,omitempty"`
F bool `xml:"f,attr,omitempty"`
C string `xml:"c,attr,omitempty"`
Cp int `xml:"cp,attr,omitempty"`
In int `xml:"in,attr,omitempty"`
Bc string `xml:"bc,attr,omitempty"`
Fc string `xml:"fc,attr,omitempty"`
I bool `xml:"i,attr,omitempty"`
Un bool `xml:"un,attr,omitempty"`
St bool `xml:"st,attr,omitempty"`
B bool `xml:"b,attr,omitempty"`
Tpls *xlsxTuples `xml:"tpls"`
X *attrValInt `xml:"x"`
}
// xlsxTuples represents members for the OLAP sheet data entry, also known as
// a tuple.
type xlsxTuples struct{}
// xlsxBoolean represents a boolean value for an item in the PivotTable.
type xlsxBoolean struct{}
// xlsxError represents an error value. The use of this item indicates that an
// error value is present in the PivotTable source. The error is recorded in
// the value attribute.
type xlsxError struct{}
// xlsxString represents a character value in a PivotTable.
type xlsxString struct {
V string `xml:"v,attr"`
U bool `xml:"u,attr,omitempty"`
F bool `xml:"f,attr,omitempty"`
C string `xml:"c,attr,omitempty"`
Cp int `xml:"cp,attr,omitempty"`
In int `xml:"in,attr,omitempty"`
Bc string `xml:"bc,attr,omitempty"`
Fc string `xml:"fc,attr,omitempty"`
I bool `xml:"i,attr,omitempty"`
Un bool `xml:"un,attr,omitempty"`
St bool `xml:"st,attr,omitempty"`
B bool `xml:"b,attr,omitempty"`
Tpls *xlsxTuples `xml:"tpls"`
X *attrValInt `xml:"x"`
}
// xlsxDateTime represents a date-time value in the PivotTable.
type xlsxDateTime struct{}
// xlsxFieldGroup represents the collection of properties for a field group.
type xlsxFieldGroup struct{}
// xlsxCacheHierarchies represents the collection of OLAP hierarchies in the
// PivotCache.
type xlsxCacheHierarchies struct{}
// xlsxKpis represents the collection of Key Performance Indicators (KPIs)
// defined on the OLAP server and stored in the PivotCache.
type xlsxKpis struct{}
// xlsxTupleCache represents the cache of OLAP sheet data members, or tuples.
type xlsxTupleCache struct{}
// xlsxCalculatedItems represents the collection of calculated items.
type xlsxCalculatedItems struct{}
// xlsxCalculatedMembers represents the collection of calculated members in an
// OLAP PivotTable.
type xlsxCalculatedMembers struct{}
// xlsxDimensions represents the collection of PivotTable OLAP dimensions.
type xlsxDimensions struct{}
// xlsxMeasureGroups represents the collection of PivotTable OLAP measure
// groups.
type xlsxMeasureGroups struct{}
// xlsxMaps represents the PivotTable OLAP measure group - Dimension maps.
type xlsxMaps struct{}
// xlsxX14PivotCacheDefinition specifies the extended properties of a pivot
// table cache definition.
type xlsxX14PivotCacheDefinition struct {
XMLName xml.Name `xml:"x14:pivotCacheDefinition"`
PivotCacheID int `xml:"pivotCacheId,attr"`
}
// decodeX14PivotCacheDefinition defines the structure used to parse the
// x14:pivotCacheDefinition element of a pivot table cache.
type decodeX14PivotCacheDefinition struct {
XMLName xml.Name `xml:"pivotCacheDefinition"`
PivotCacheID int `xml:"pivotCacheId,attr"`
}
================================================
FILE: xmlPivotTable.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxPivotTableDefinition represents the PivotTable root element for
// non-null PivotTables. There exists one pivotTableDefinition for each
// PivotTableDefinition part
type xlsxPivotTableDefinition struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"`
Name string `xml:"name,attr"`
CacheID int `xml:"cacheId,attr"`
ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"`
ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"`
ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"`
ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"`
ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"`
ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"`
DataOnRows bool `xml:"dataOnRows,attr,omitempty"`
DataPosition int `xml:"dataPosition,attr,omitempty"`
DataCaption string `xml:"dataCaption,attr"`
GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"`
ErrorCaption string `xml:"errorCaption,attr,omitempty"`
ShowError *bool `xml:"showError,attr"`
MissingCaption string `xml:"missingCaption,attr,omitempty"`
ShowMissing bool `xml:"showMissing,attr,omitempty"`
PageStyle string `xml:"pageStyle,attr,omitempty"`
PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"`
VacatedStyle string `xml:"vacatedStyle,attr,omitempty"`
Tag string `xml:"tag,attr,omitempty"`
UpdatedVersion int `xml:"updatedVersion,attr,omitempty"`
MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"`
AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"`
ShowItems bool `xml:"showItems,attr,omitempty"`
EditData bool `xml:"editData,attr,omitempty"`
DisableFieldList bool `xml:"disableFieldList,attr,omitempty"`
ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"`
VisualTotals bool `xml:"visualTotals,attr,omitempty"`
ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"`
ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"`
ShowDrill *bool `xml:"showDrill,attr"`
PrintDrill bool `xml:"printDrill,attr,omitempty"`
ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"`
ShowDataTips bool `xml:"showDataTips,attr,omitempty"`
EnableWizard bool `xml:"enableWizard,attr,omitempty"`
EnableDrill bool `xml:"enableDrill,attr,omitempty"`
EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"`
PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"`
UseAutoFormatting *bool `xml:"useAutoFormatting,attr"`
PageWrap int `xml:"pageWrap,attr,omitempty"`
PageOverThenDown *bool `xml:"pageOverThenDown,attr"`
SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"`
RowGrandTotals *bool `xml:"rowGrandTotals,attr"`
ColGrandTotals *bool `xml:"colGrandTotals,attr"`
FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"`
ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"`
MergeItem *bool `xml:"mergeItem,attr"`
ShowDropZones bool `xml:"showDropZones,attr,omitempty"`
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
Indent int `xml:"indent,attr,omitempty"`
ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"`
ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"`
ShowHeaders bool `xml:"showHeaders,attr,omitempty"`
Compact *bool `xml:"compact,attr"`
Outline *bool `xml:"outline,attr"`
OutlineData bool `xml:"outlineData,attr,omitempty"`
CompactData *bool `xml:"compactData,attr"`
Published bool `xml:"published,attr,omitempty"`
GridDropZones bool `xml:"gridDropZones,attr,omitempty"`
Immersive bool `xml:"immersive,attr,omitempty"`
MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"`
ChartFormat int `xml:"chartFormat,attr,omitempty"`
RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"`
ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"`
FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"`
MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"`
CustomListSort bool `xml:"customListSort,attr,omitempty"`
Location *xlsxLocation `xml:"location"`
PivotFields *xlsxPivotFields `xml:"pivotFields"`
RowFields *xlsxRowFields `xml:"rowFields"`
RowItems *xlsxRowItems `xml:"rowItems"`
ColFields *xlsxColFields `xml:"colFields"`
ColItems *xlsxColItems `xml:"colItems"`
PageFields *xlsxPageFields `xml:"pageFields"`
DataFields *xlsxDataFields `xml:"dataFields"`
ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"`
PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"`
}
// xlsxLocation represents location information for the PivotTable.
type xlsxLocation struct {
Ref string `xml:"ref,attr"`
FirstHeaderRow int `xml:"firstHeaderRow,attr"`
FirstDataRow int `xml:"firstDataRow,attr"`
FirstDataCol int `xml:"firstDataCol,attr"`
RowPageCount int `xml:"rowPageCount,attr,omitempty"`
ColPageCount int `xml:"colPageCount,attr,omitempty"`
}
// xlsxPivotFields represents the collection of fields that appear on the
// PivotTable.
type xlsxPivotFields struct {
Count int `xml:"count,attr"`
PivotField []*xlsxPivotField `xml:"pivotField"`
}
// xlsxPivotField represents a single field in the PivotTable. This element
// contains information about the field, including the collection of items in
// the field.
type xlsxPivotField struct {
Name string `xml:"name,attr,omitempty"`
Axis string `xml:"axis,attr,omitempty"`
DataField bool `xml:"dataField,attr,omitempty"`
SubtotalCaption string `xml:"subtotalCaption,attr,omitempty"`
ShowDropDowns bool `xml:"showDropDowns,attr,omitempty"`
HiddenLevel bool `xml:"hiddenLevel,attr,omitempty"`
UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"`
Compact *bool `xml:"compact,attr"`
AllDrilled bool `xml:"allDrilled,attr,omitempty"`
NumFmtID string `xml:"numFmtId,attr,omitempty"`
Outline *bool `xml:"outline,attr"`
SubtotalTop bool `xml:"subtotalTop,attr,omitempty"`
DragToRow bool `xml:"dragToRow,attr,omitempty"`
DragToCol bool `xml:"dragToCol,attr,omitempty"`
MultipleItemSelectionAllowed bool `xml:"multipleItemSelectionAllowed,attr,omitempty"`
DragToPage bool `xml:"dragToPage,attr,omitempty"`
DragToData bool `xml:"dragToData,attr,omitempty"`
DragOff bool `xml:"dragOff,attr,omitempty"`
ShowAll bool `xml:"showAll,attr"`
InsertBlankRow bool `xml:"insertBlankRow,attr,omitempty"`
ServerField bool `xml:"serverField,attr,omitempty"`
InsertPageBreak bool `xml:"insertPageBreak,attr,omitempty"`
AutoShow bool `xml:"autoShow,attr,omitempty"`
TopAutoShow bool `xml:"topAutoShow,attr,omitempty"`
HideNewItems bool `xml:"hideNewItems,attr,omitempty"`
MeasureFilter bool `xml:"measureFilter,attr,omitempty"`
IncludeNewItemsInFilter bool `xml:"includeNewItemsInFilter,attr,omitempty"`
ItemPageCount int `xml:"itemPageCount,attr,omitempty"`
SortType string `xml:"sortType,attr,omitempty"`
DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"`
NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"`
RankBy int `xml:"rankBy,attr,omitempty"`
DefaultSubtotal *bool `xml:"defaultSubtotal,attr"`
SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"`
CountASubtotal bool `xml:"countASubtotal,attr,omitempty"`
AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"`
MaxSubtotal bool `xml:"maxSubtotal,attr,omitempty"`
MinSubtotal bool `xml:"minSubtotal,attr,omitempty"`
ProductSubtotal bool `xml:"productSubtotal,attr,omitempty"`
CountSubtotal bool `xml:"countSubtotal,attr,omitempty"`
StdDevSubtotal bool `xml:"stdDevSubtotal,attr,omitempty"`
StdDevPSubtotal bool `xml:"stdDevPSubtotal,attr,omitempty"`
VarSubtotal bool `xml:"varSubtotal,attr,omitempty"`
VarPSubtotal bool `xml:"varPSubtotal,attr,omitempty"`
ShowPropCell bool `xml:"showPropCell,attr,omitempty"`
ShowPropTip bool `xml:"showPropTip,attr,omitempty"`
ShowPropAsCaption bool `xml:"showPropAsCaption,attr,omitempty"`
DefaultAttributeDrillState bool `xml:"defaultAttributeDrillState,attr,omitempty"`
Items *xlsxItems `xml:"items"`
AutoSortScope *xlsxAutoSortScope `xml:"autoSortScope"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxItems represents the collection of items in a PivotTable field. The
// items in the collection are ordered by index. Items represent the unique
// entries from the field in the source data.
type xlsxItems struct {
Count int `xml:"count,attr"`
Item []*xlsxItem `xml:"item"`
}
// xlsxItem represents a single item in PivotTable field.
type xlsxItem struct {
N string `xml:"n,attr,omitempty"`
T string `xml:"t,attr,omitempty"`
H bool `xml:"h,attr,omitempty"`
S bool `xml:"s,attr,omitempty"`
SD bool `xml:"sd,attr,omitempty"`
F bool `xml:"f,attr,omitempty"`
M bool `xml:"m,attr,omitempty"`
C bool `xml:"c,attr,omitempty"`
X *int `xml:"x,attr,omitempty"`
D bool `xml:"d,attr,omitempty"`
E bool `xml:"e,attr,omitempty"`
}
// xlsxAutoSortScope represents the sorting scope for the PivotTable.
type xlsxAutoSortScope struct{}
// xlsxRowFields represents the collection of row fields for the PivotTable.
type xlsxRowFields struct {
Count int `xml:"count,attr"`
Field []*xlsxField `xml:"field"`
}
// xlsxField represents a generic field that can appear either on the column
// or the row region of the PivotTable. There areas many elements as there
// are item values in any particular column or row.
type xlsxField struct {
X int `xml:"x,attr"`
}
// xlsxRowItems represents the collection of items in row axis of the
// PivotTable.
type xlsxRowItems struct {
Count int `xml:"count,attr"`
I []*xlsxI `xml:"i"`
}
// xlsxI represents the collection of items in the row region of the
// PivotTable.
type xlsxI struct {
X []*xlsxX `xml:"x"`
}
// xlsxX represents an array of indexes to cached shared item values.
type xlsxX struct{}
// xlsxColFields represents the collection of fields that are on the column
// axis of the PivotTable.
type xlsxColFields struct {
Count int `xml:"count,attr"`
Field []*xlsxField `xml:"field"`
}
// xlsxColItems represents the collection of column items of the PivotTable.
type xlsxColItems struct {
Count int `xml:"count,attr"`
I []*xlsxI `xml:"i"`
}
// xlsxPageFields represents the collection of items in the page or report
// filter region of the PivotTable.
type xlsxPageFields struct {
Count int `xml:"count,attr"`
PageField []*xlsxPageField `xml:"pageField"`
}
// xlsxPageField represents a field on the page or report filter of the
// PivotTable.
type xlsxPageField struct {
Fld int `xml:"fld,attr"`
Item int `xml:"item,attr,omitempty"`
Hier int `xml:"hier,attr,omitempty"`
Name string `xml:"name,attr,omitempty"`
Cap string `xml:"cap,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxDataFields represents the collection of items in the data region of the
// PivotTable.
type xlsxDataFields struct {
Count int `xml:"count,attr"`
DataField []*xlsxDataField `xml:"dataField"`
}
// xlsxDataField represents a field from a source list, table, or database
// that contains data that is summarized in a PivotTable.
type xlsxDataField struct {
Name string `xml:"name,attr,omitempty"`
Fld int `xml:"fld,attr"`
Subtotal string `xml:"subtotal,attr,omitempty"`
ShowDataAs string `xml:"showDataAs,attr,omitempty"`
BaseField int `xml:"baseField,attr,omitempty"`
BaseItem int64 `xml:"baseItem,attr,omitempty"`
NumFmtID int `xml:"numFmtId,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxConditionalFormats represents the collection of conditional formats
// applied to a PivotTable.
type xlsxConditionalFormats struct{}
// xlsxPivotTableStyleInfo represent information on style applied to the
// PivotTable.
type xlsxPivotTableStyleInfo struct {
Name string `xml:"name,attr"`
ShowRowHeaders bool `xml:"showRowHeaders,attr"`
ShowColHeaders bool `xml:"showColHeaders,attr"`
ShowRowStripes bool `xml:"showRowStripes,attr,omitempty"`
ShowColStripes bool `xml:"showColStripes,attr,omitempty"`
ShowLastColumn bool `xml:"showLastColumn,attr,omitempty"`
}
================================================
FILE: xmlSharedStrings.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxSST directly maps the sst element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
// be stored directly inside spreadsheet cell elements; however, storing the
// same value inside multiple cell elements can result in very large worksheet
// Parts, possibly resulting in performance degradation. The Shared String Table
// is an indexed list of string values, shared across the workbook, which allows
// implementations to store values only once.
type xlsxSST struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"`
Count int `xml:"count,attr"`
UniqueCount int `xml:"uniqueCount,attr"`
SI []xlsxSI `xml:"si"`
}
// xlsxSI (String Item) is the representation of an individual string in the
// Shared String table. If the string is just a simple string with formatting
// applied at the cell level, then the String Item (si) should contain a
// single text element used to express the string. However, if the string in
// the cell is more complex - i.e., has formatting applied at the character
// level - then the string item shall consist of multiple rich text runs which
// collectively are used to express the string.
type xlsxSI struct {
T *xlsxT `xml:"t,omitempty"`
R []xlsxR `xml:"r"`
RPh []*xlsxPhoneticRun `xml:"rPh"`
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
}
// xlsxR represents a run of rich text. A rich text run is a region of text
// that share a common set of properties, such as formatting properties. The
// properties are defined in the rPr element, and the text displayed to the
// user is defined in the Text (t) element.
type xlsxR struct {
XMLName xml.Name `xml:"r"`
RPr *xlsxRPr `xml:"rPr"`
T *xlsxT `xml:"t"`
}
// xlsxT directly maps the t element in the run properties.
type xlsxT struct {
XMLName xml.Name `xml:"t"`
Space xml.Attr `xml:"space,attr,omitempty"`
Val string `xml:",chardata"`
}
// xlsxRPr (Run Properties) specifies a set of run properties which shall be
// applied to the contents of the parent run after all style formatting has been
// applied to the text. These properties are defined as direct formatting, since
// they are directly applied to the run and supersede any formatting from
// styles.
type xlsxRPr struct {
RFont *attrValString `xml:"rFont"`
Charset *attrValInt `xml:"charset"`
Family *attrValInt `xml:"family"`
B *attrValBool `xml:"b"`
I *attrValBool `xml:"i"`
Strike *attrValBool `xml:"strike"`
Outline *attrValBool `xml:"outline"`
Shadow *attrValBool `xml:"shadow"`
Condense *attrValBool `xml:"condense"`
Extend *attrValBool `xml:"extend"`
Color *xlsxColor `xml:"color"`
Sz *attrValFloat `xml:"sz"`
U *attrValString `xml:"u"`
VertAlign *attrValString `xml:"vertAlign"`
Scheme *attrValString `xml:"scheme"`
}
// RichTextRun directly maps the settings of the rich text run.
type RichTextRun struct {
Font *Font
Text string
}
================================================
FILE: xmlSlicers.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxSlicers directly maps the slicers element that specifies a slicer view on
// the worksheet.
type xlsxSlicers struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicers"`
XMLNSXMC string `xml:"xmlns:mc,attr"`
XMLNSX string `xml:"xmlns:x,attr"`
XMLNSXR10 string `xml:"xmlns:xr10,attr"`
Slicer []xlsxSlicer `xml:"slicer"`
}
// xlsxSlicer is a complex type that specifies a slicer view.
type xlsxSlicer struct {
Name string `xml:"name,attr"`
XR10UID string `xml:"xr10:uid,attr,omitempty"`
Cache string `xml:"cache,attr"`
Caption string `xml:"caption,attr,omitempty"`
StartItem *int `xml:"startItem,attr"`
ColumnCount *int `xml:"columnCount,attr"`
ShowCaption *bool `xml:"showCaption,attr"`
Level int `xml:"level,attr,omitempty"`
Style string `xml:"style,attr,omitempty"`
LockedPosition bool `xml:"lockedPosition,attr,omitempty"`
RowHeight int `xml:"rowHeight,attr"`
}
// slicerCacheDefinition directly maps the slicerCacheDefinition element that
// specifies a slicer cache.
type xlsxSlicerCacheDefinition struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicerCacheDefinition"`
XMLNSXMC string `xml:"xmlns:mc,attr"`
XMLNSX string `xml:"xmlns:x,attr"`
XMLNSX15 string `xml:"xmlns:x15,attr,omitempty"`
XMLNSXR10 string `xml:"xmlns:xr10,attr"`
Name string `xml:"name,attr"`
XR10UID string `xml:"xr10:uid,attr,omitempty"`
SourceName string `xml:"sourceName,attr"`
PivotTables *xlsxSlicerCachePivotTables `xml:"pivotTables"`
Data *xlsxSlicerCacheData `xml:"data"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxSlicerCachePivotTables is a complex type that specifies a group of
// pivotTable elements that specify the PivotTable views that are filtered by
// the slicer cache.
type xlsxSlicerCachePivotTables struct {
PivotTable []xlsxSlicerCachePivotTable `xml:"pivotTable"`
}
// xlsxSlicerCachePivotTable is a complex type that specifies a PivotTable view
// filtered by a slicer cache.
type xlsxSlicerCachePivotTable struct {
TabID int `xml:"tabId,attr"`
Name string `xml:"name,attr"`
}
// xlsxSlicerCacheData is a complex type that specifies a data source for the
// slicer cache.
type xlsxSlicerCacheData struct {
OLAP *xlsxInnerXML `xml:"olap"`
Tabular *xlsxTabularSlicerCache `xml:"tabular"`
}
// xlsxTabularSlicerCache is a complex type that specifies non-OLAP slicer items
// that are cached within this slicer cache and properties of the slicer cache
// specific to non-OLAP slicer items.
type xlsxTabularSlicerCache struct {
PivotCacheID int `xml:"pivotCacheId,attr"`
SortOrder string `xml:"sortOrder,attr,omitempty"`
CustomListSort *bool `xml:"customListSort,attr"`
ShowMissing *bool `xml:"showMissing,attr"`
CrossFilter string `xml:"crossFilter,attr,omitempty"`
Items *xlsxTabularSlicerCacheItems `xml:"items"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxTabularSlicerCacheItems is a complex type that specifies non-OLAP slicer
// items that are cached within this slicer cache.
type xlsxTabularSlicerCacheItems struct {
Count int `xml:"count,attr,omitempty"`
I []xlsxTabularSlicerCacheItem `xml:"i"`
}
// xlsxTabularSlicerCacheItem is a complex type that specifies a non-OLAP slicer
// item that is cached within this slicer cache.
type xlsxTabularSlicerCacheItem struct {
X int `xml:"x,attr"`
S bool `xml:"s,attr,omitempty"`
ND bool `xml:"nd,attr,omitempty"`
}
// xlsxTableSlicerCache specifies a table data source for the slicer cache.
type xlsxTableSlicerCache struct {
XMLName xml.Name `xml:"x15:tableSlicerCache"`
TableID int `xml:"tableId,attr"`
Column int `xml:"column,attr"`
SortOrder string `xml:"sortOrder,attr,omitempty"`
CustomListSort *bool `xml:"customListSort,attr"`
CrossFilter string `xml:"crossFilter,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxX14SlicerList specifies a list of slicer.
type xlsxX14SlicerList struct {
XMLName xml.Name `xml:"x14:slicerList"`
Slicer []*xlsxX14Slicer `xml:"x14:slicer"`
}
// xlsxX14Slicer specifies a slicer view,
type xlsxX14Slicer struct {
XMLName xml.Name `xml:"x14:slicer"`
RID string `xml:"r:id,attr"`
}
// xlsxX14SlicerCaches directly maps the x14:slicerCache element.
type xlsxX14SlicerCaches struct {
XMLName xml.Name `xml:"x14:slicerCaches"`
XMLNS string `xml:"xmlns:x14,attr"`
Content string `xml:",innerxml"`
}
// xlsxX15SlicerCaches directly maps the x14:slicerCache element.
type xlsxX14SlicerCache struct {
XMLName xml.Name `xml:"x14:slicerCache"`
RID string `xml:"r:id,attr"`
}
// xlsxX15SlicerCaches directly maps the x15:slicerCaches element.
type xlsxX15SlicerCaches struct {
XMLName xml.Name `xml:"x15:slicerCaches"`
XMLNS string `xml:"xmlns:x14,attr"`
Content string `xml:",innerxml"`
}
// decodeTableSlicerCache defines the structure used to parse the
// x15:tableSlicerCache element of the table slicer cache.
type decodeTableSlicerCache struct {
XMLName xml.Name `xml:"tableSlicerCache"`
TableID int `xml:"tableId,attr"`
Column int `xml:"column,attr"`
SortOrder string `xml:"sortOrder,attr"`
}
// decodeSlicerList defines the structure used to parse the x14:slicerList
// element of a list of slicer.
type decodeSlicerList struct {
XMLName xml.Name `xml:"slicerList"`
Slicer []*decodeSlicer `xml:"slicer"`
}
// decodeSlicer defines the structure used to parse the x14:slicer element of a
// slicer.
type decodeSlicer struct {
RID string `xml:"id,attr"`
}
// decodeSlicerCaches defines the structure used to parse the
// x14:slicerCaches and x15:slicerCaches element of a slicer cache.
type decodeSlicerCaches struct {
XMLName xml.Name `xml:"slicerCaches"`
Content string `xml:",innerxml"`
}
// xlsxTimelines is a mechanism for filtering data in pivot table views, cube
// functions and charts based on non-worksheet pivot tables. In the case of
// using OLAP Timeline source data, a Timeline is based on a key attribute of
// an OLAP hierarchy. In the case of using native Timeline source data, a
// Timeline is based on a data table column.
type xlsxTimelines struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main timelines"`
XMLNSXMC string `xml:"xmlns:mc,attr"`
XMLNSX string `xml:"xmlns:x,attr"`
XMLNSXR10 string `xml:"xmlns:xr10,attr"`
Timeline []xlsxTimeline `xml:"timeline"`
}
// xlsxTimeline is timeline view specifies the display of a timeline on a
// worksheet.
type xlsxTimeline struct {
Name string `xml:"name,attr"`
XR10UID string `xml:"xr10:uid,attr,omitempty"`
Cache string `xml:"cache,attr"`
Caption string `xml:"caption,attr,omitempty"`
ShowHeader *bool `xml:"showHeader,attr"`
ShowSelectionLabel *bool `xml:"showSelectionLabel,attr"`
ShowTimeLevel *bool `xml:"showTimeLevel,attr"`
ShowHorizontalScrollbar *bool `xml:"showHorizontalScrollbar,attr"`
Level int `xml:"level,attr"`
SelectionLevel int `xml:"selectionLevel,attr"`
ScrollPosition string `xml:"scrollPosition,attr,omitempty"`
Style string `xml:"style,attr,omitempty"`
}
================================================
FILE: xmlStyles.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxStyleSheet is the root element of the Styles part.
type xlsxStyleSheet struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
NumFmts *xlsxNumFmts `xml:"numFmts"`
Fonts *xlsxFonts `xml:"fonts"`
Fills *xlsxFills `xml:"fills"`
Borders *xlsxBorders `xml:"borders"`
CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs"`
CellXfs *xlsxCellXfs `xml:"cellXfs"`
CellStyles *xlsxCellStyles `xml:"cellStyles"`
Dxfs *xlsxDxfs `xml:"dxfs"`
TableStyles *xlsxTableStyles `xml:"tableStyles"`
Colors *xlsxStyleColors `xml:"colors"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxAlignment formatting information pertaining to text alignment in cells.
// There are a variety of choices for how text is aligned both horizontally and
// vertically, as well as indentation settings, and so on.
type xlsxAlignment struct {
Horizontal string `xml:"horizontal,attr,omitempty"`
Indent int `xml:"indent,attr,omitempty"`
JustifyLastLine bool `xml:"justifyLastLine,attr,omitempty"`
ReadingOrder uint64 `xml:"readingOrder,attr,omitempty"`
RelativeIndent int `xml:"relativeIndent,attr,omitempty"`
ShrinkToFit bool `xml:"shrinkToFit,attr,omitempty"`
TextRotation int `xml:"textRotation,attr,omitempty"`
Vertical string `xml:"vertical,attr,omitempty"`
WrapText bool `xml:"wrapText,attr,omitempty"`
}
// xlsxProtection (Protection Properties) contains protection properties
// associated with the cell. Each cell has protection properties that can be
// set. The cell protection properties do not take effect unless the sheet has
// been protected.
type xlsxProtection struct {
Hidden *bool `xml:"hidden,attr"`
Locked *bool `xml:"locked,attr"`
}
// xlsxLine expresses a single set of cell border.
type xlsxLine struct {
Style string `xml:"style,attr,omitempty"`
Color *xlsxColor `xml:"color"`
}
// xlsxColor is a common mapping used for both the fgColor and bgColor elements.
// Foreground color of the cell fill pattern. Cell fill patterns operate with
// two colors: a background color and a foreground color. These combine
// to make a patterned cell fill. Background color of the cell fill pattern.
// Cell fill patterns operate with two colors: a background color and a
// foreground color. These combine to make a patterned cell fill.
type xlsxColor struct {
Auto bool `xml:"auto,attr,omitempty"`
RGB string `xml:"rgb,attr,omitempty"`
Indexed int `xml:"indexed,attr,omitempty"`
Theme *int `xml:"theme,attr"`
Tint float64 `xml:"tint,attr,omitempty"`
}
// xlsxFonts directly maps the font element. This element contains all font
// definitions for this workbook.
type xlsxFonts struct {
Count int `xml:"count,attr"`
Font []*xlsxFont `xml:"font"`
}
// xlsxFont directly maps the font element. This element defines the
// properties for one of the fonts used in this workbook.
type xlsxFont struct {
Name *attrValString `xml:"name"`
Charset *attrValInt `xml:"charset"`
Family *attrValInt `xml:"family"`
B *attrValBool `xml:"b"`
I *attrValBool `xml:"i"`
Strike *attrValBool `xml:"strike"`
Outline *attrValBool `xml:"outline"`
Shadow *attrValBool `xml:"shadow"`
Condense *attrValBool `xml:"condense"`
Extend *attrValBool `xml:"extend"`
Color *xlsxColor `xml:"color"`
Sz *attrValFloat `xml:"sz"`
U *attrValString `xml:"u"`
VertAlign *attrValString `xml:"vertAlign"`
Scheme *attrValString `xml:"scheme"`
}
// xlsxFills directly maps the fills' element. This element defines the cell
// fills portion of the Styles part, consisting of a sequence of fill records. A
// cell fill consists of a background color, foreground color, and pattern to be
// applied across the cell.
type xlsxFills struct {
Count int `xml:"count,attr"`
Fill []*xlsxFill `xml:"fill"`
}
// xlsxFill directly maps the fill element. This element specifies fill
// formatting.
type xlsxFill struct {
PatternFill *xlsxPatternFill `xml:"patternFill"`
GradientFill *xlsxGradientFill `xml:"gradientFill"`
}
// xlsxPatternFill is used to specify cell fill information for pattern and
// solid color cell fills. For solid cell fills (no pattern), fgColor is used.
// For cell fills with patterns specified, then the cell fill color is
// specified by the bgColor element.
type xlsxPatternFill struct {
PatternType string `xml:"patternType,attr,omitempty"`
FgColor *xlsxColor `xml:"fgColor"`
BgColor *xlsxColor `xml:"bgColor"`
}
// xlsxGradientFill defines a gradient-style cell fill. Gradient cell fills can
// use one or two colors as the end points of color interpolation.
type xlsxGradientFill struct {
Bottom float64 `xml:"bottom,attr,omitempty"`
Degree float64 `xml:"degree,attr,omitempty"`
Left float64 `xml:"left,attr,omitempty"`
Right float64 `xml:"right,attr,omitempty"`
Top float64 `xml:"top,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Stop []*xlsxGradientFillStop `xml:"stop"`
}
// xlsxGradientFillStop directly maps the stop element.
type xlsxGradientFillStop struct {
Position float64 `xml:"position,attr"`
Color xlsxColor `xml:"color,omitempty"`
}
// xlsxBorders directly maps the borders' element. This element contains borders
// formatting information, specifying all border definitions for all cells in
// the workbook.
type xlsxBorders struct {
Count int `xml:"count,attr"`
Border []*xlsxBorder `xml:"border"`
}
// xlsxBorder directly maps the border element. Expresses a single set of cell
// border formats (left, right, top, bottom, diagonal). Color is optional. When
// missing, 'automatic' is implied.
type xlsxBorder struct {
DiagonalDown bool `xml:"diagonalDown,attr,omitempty"`
DiagonalUp bool `xml:"diagonalUp,attr,omitempty"`
Outline bool `xml:"outline,attr,omitempty"`
Left *xlsxLine `xml:"left"`
Right *xlsxLine `xml:"right"`
Top *xlsxLine `xml:"top"`
Bottom *xlsxLine `xml:"bottom"`
Diagonal *xlsxLine `xml:"diagonal"`
Vertical *xlsxLine `xml:"vertical"`
Horizontal *xlsxLine `xml:"horizontal"`
}
// xlsxCellStyles directly maps the cellStyles element. This element contains
// the named cell styles, consisting of a sequence of named style records. A
// named cell style is a collection of direct or themed formatting (e.g., cell
// border, cell fill, and font type/size/style) grouped together into a single
// named style, and can be applied to a cell.
type xlsxCellStyles struct {
XMLName xml.Name `xml:"cellStyles"`
Count int `xml:"count,attr"`
CellStyle []*xlsxCellStyle `xml:"cellStyle"`
}
// xlsxCellStyle directly maps the cellStyle element. This element represents
// the name and related formatting records for a named cell style in this
// workbook.
type xlsxCellStyle struct {
XMLName xml.Name `xml:"cellStyle"`
Name string `xml:"name,attr"`
XfID int `xml:"xfId,attr"`
BuiltInID *int `xml:"builtinId,attr"`
ILevel *int `xml:"iLevel,attr"`
Hidden *bool `xml:"hidden,attr"`
CustomBuiltIn *bool `xml:"customBuiltin,attr"`
}
// xlsxCellStyleXfs directly maps the cellStyleXfs element. This element
// contains the master formatting records (xf's) which define the formatting for
// all named cell styles in this workbook. Master formatting records reference
// individual elements of formatting (e.g., number format, font definitions,
// cell fills, etc.) by specifying a zero-based index into those collections.
// Master formatting records also specify whether to apply or ignore particular
// aspects of formatting.
type xlsxCellStyleXfs struct {
Count int `xml:"count,attr"`
Xf []xlsxXf `xml:"xf,omitempty"`
}
// xlsxXf directly maps the xf element. A single xf element describes all the
// formatting for a cell.
type xlsxXf struct {
NumFmtID *int `xml:"numFmtId,attr"`
FontID *int `xml:"fontId,attr"`
FillID *int `xml:"fillId,attr"`
BorderID *int `xml:"borderId,attr"`
XfID *int `xml:"xfId,attr"`
QuotePrefix *bool `xml:"quotePrefix,attr"`
PivotButton *bool `xml:"pivotButton,attr"`
ApplyNumberFormat *bool `xml:"applyNumberFormat,attr"`
ApplyFont *bool `xml:"applyFont,attr"`
ApplyFill *bool `xml:"applyFill,attr"`
ApplyBorder *bool `xml:"applyBorder,attr"`
ApplyAlignment *bool `xml:"applyAlignment,attr"`
ApplyProtection *bool `xml:"applyProtection,attr"`
Alignment *xlsxAlignment `xml:"alignment"`
Protection *xlsxProtection `xml:"protection"`
}
// xlsxCellXfs directly maps the cellXfs element. This element contains the
// master formatting records (xf) which define the formatting applied to cells
// in this workbook. These records are the starting point for determining the
// formatting for a cell. Cells in the Sheet Part reference the xf records by
// zero-based index.
type xlsxCellXfs struct {
Count int `xml:"count,attr"`
Xf []xlsxXf `xml:"xf,omitempty"`
}
// xlsxDxfs directly maps the dxfs element. This element contains the master
// differential formatting records (dxf's) which define formatting for all
// non-cell formatting in this workbook. Whereas xf records fully specify a
// particular aspect of formatting (e.g., cell borders) by referencing those
// formatting definitions elsewhere in the Styles part, dxf records specify
// incremental (or differential) aspects of formatting directly inline within
// the dxf element. The dxf formatting is to be applied on top of or in addition
// to any formatting already present on the object using the dxf record.
type xlsxDxfs struct {
Count int `xml:"count,attr"`
Dxfs []*xlsxDxf `xml:"dxf"`
}
// xlsxDxf directly maps the dxf element. A single dxf record, expressing
// incremental formatting to be applied.
type xlsxDxf struct {
Font *xlsxFont `xml:"font"`
NumFmt *xlsxNumFmt `xml:"numFmt"`
Fill *xlsxFill `xml:"fill"`
Alignment *xlsxAlignment `xml:"alignment"`
Border *xlsxBorder `xml:"border"`
Protection *xlsxProtection `xml:"protection"`
ExtLst *xlsxPositiveSize2D `xml:"extLst"`
}
// xlsxTableStyles directly maps the tableStyles element. This element
// represents a collection of Table style definitions for Table styles and
// PivotTable styles used in this workbook. It consists of a sequence of
// tableStyle records, each defining a single Table style.
type xlsxTableStyles struct {
Count int `xml:"count,attr"`
DefaultPivotStyle string `xml:"defaultPivotStyle,attr"`
DefaultTableStyle string `xml:"defaultTableStyle,attr"`
TableStyles []*xlsxTableStyle `xml:"tableStyle"`
}
// xlsxTableStyle directly maps the tableStyle element. This element represents
// a single table style definition that indicates how a spreadsheet application
// should format and display a table.
type xlsxTableStyle struct {
Name string `xml:"name,attr,omitempty"`
Pivot int `xml:"pivot,attr"`
Count int `xml:"count,attr,omitempty"`
Table bool `xml:"table,attr,omitempty"`
TableStyleElement string `xml:",innerxml"`
}
// xlsxNumFmts directly maps the numFmts element. This element defines the
// number formats in this workbook, consisting of a sequence of numFmt records,
// where each numFmt record defines a particular number format, indicating how
// to format and render the numeric value of a cell.
type xlsxNumFmts struct {
Count int `xml:"count,attr"`
NumFmt []*xlsxNumFmt `xml:"numFmt"`
}
// xlsxNumFmt directly maps the numFmt element. This element specifies number
// format properties which indicate how to format and render the numeric value
// of a cell.
type xlsxNumFmt struct {
NumFmtID int `xml:"numFmtId,attr"`
FormatCode string `xml:"formatCode,attr"`
FormatCode16 string `xml:"http://schemas.microsoft.com/office/spreadsheetml/2015/02/main formatCode16,attr,omitempty"`
}
// xlsxIndexedColors directly maps the single ARGB entry for the corresponding
// color index.
type xlsxIndexedColors struct {
RgbColor []xlsxColor `xml:"rgbColor"`
}
// xlsxStyleColors directly maps the colors' element. Color information
// associated with this style sheet. This collection is written whenever the
// legacy color palette has been modified (backwards compatibility settings) or
// a custom color has been selected while using this workbook.
type xlsxStyleColors struct {
IndexedColors *xlsxIndexedColors `xml:"indexedColors"`
MruColors *xlsxInnerXML `xml:"mruColors"`
}
// Alignment directly maps the alignment settings of the cells.
type Alignment struct {
Horizontal string
Indent int
JustifyLastLine bool
ReadingOrder uint64
RelativeIndent int
ShrinkToFit bool
TextRotation int
Vertical string
WrapText bool
}
// Border directly maps the border settings of the cells.
type Border struct {
Type string
Color string
Style int
}
// Font directly maps the font settings of the fonts.
type Font struct {
Bold bool
Italic bool
Underline string
Family string
Size float64
Strike bool
Color string
ColorIndexed int
ColorTheme *int
ColorTint float64
VertAlign string
Charset *int
}
// Fill directly maps the fill settings of the cells.
type Fill struct {
Type string
Pattern int
Color []string
Shading int
Transparency int
}
// Protection directly maps the protection settings of the cells.
type Protection struct {
Hidden bool
Locked bool
}
// Style directly maps the style settings of the cells.
type Style struct {
Border []Border
Fill Fill
Font *Font
Alignment *Alignment
Protection *Protection
NumFmt int
DecimalPlaces *int
CustomNumFmt *string
NegRed bool
}
================================================
FILE: xmlTable.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxTable directly maps the table element. A table helps organize and provide
// structure to list of information in a worksheet. Tables have clearly labeled
// columns, rows, and data regions. Tables make it easier for users to sort,
// analyze, format, manage, add, and delete information. This element is the
// root element for a table that is not a single cell XML table.
type xlsxTable struct {
XMLName xml.Name `xml:"table"`
XMLNS string `xml:"xmlns,attr"`
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
DisplayName string `xml:"displayName,attr,omitempty"`
Comment string `xml:"comment,attr,omitempty"`
Ref string `xml:"ref,attr"`
TableType string `xml:"tableType,attr,omitempty"`
HeaderRowCount *int `xml:"headerRowCount,attr"`
InsertRow bool `xml:"insertRow,attr,omitempty"`
InsertRowShift bool `xml:"insertRowShift,attr,omitempty"`
TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"`
TotalsRowShown *bool `xml:"totalsRowShown,attr"`
Published bool `xml:"published,attr,omitempty"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
TableBorderDxfID int `xml:"tableBorderDxfId,attr,omitempty"`
TotalsRowBorderDxfID int `xml:"totalsRowBorderDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
ConnectionID int `xml:"connectionId,attr,omitempty"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
TableColumns *xlsxTableColumns `xml:"tableColumns"`
TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"`
}
// xlsxAutoFilter temporarily hides rows based on a filter criteria, which is
// applied column by column to a table of data in the worksheet. This collection
// expresses AutoFilter settings.
type xlsxAutoFilter struct {
XMLName xml.Name `xml:"autoFilter"`
Ref string `xml:"ref,attr"`
FilterColumn []*xlsxFilterColumn `xml:"filterColumn"`
}
// xlsxFilterColumn directly maps the filterColumn element. The filterColumn
// collection identifies a particular column in the AutoFilter range and
// specifies filter information that has been applied to this column. If a
// column in the AutoFilter range has no criteria specified, then there is no
// corresponding filterColumn collection expressed for that column.
type xlsxFilterColumn struct {
ColID int `xml:"colId,attr"`
HiddenButton bool `xml:"hiddenButton,attr,omitempty"`
ShowButton bool `xml:"showButton,attr,omitempty"`
CustomFilters *xlsxCustomFilters `xml:"customFilters"`
Filters *xlsxFilters `xml:"filters"`
ColorFilter *xlsxColorFilter `xml:"colorFilter"`
DynamicFilter *xlsxDynamicFilter `xml:"dynamicFilter"`
IconFilter *xlsxIconFilter `xml:"iconFilter"`
Top10 *xlsxTop10 `xml:"top10"`
}
// xlsxCustomFilters directly maps the customFilters element. When there is more
// than one custom filter criteria to apply (an 'and' or 'or' joining two
// criteria), then this element groups the customFilter elements together.
type xlsxCustomFilters struct {
And bool `xml:"and,attr,omitempty"`
CustomFilter []*xlsxCustomFilter `xml:"customFilter"`
}
// xlsxCustomFilter directly maps the customFilter element. A custom AutoFilter
// specifies an operator and a value. There can be at most two customFilters
// specified, and in that case the parent element specifies whether the two
// conditions are joined by 'and' or 'or'. For any cells whose values do not
// meet the specified criteria, the corresponding rows shall be hidden from view
// when the filter is applied.
type xlsxCustomFilter struct {
Operator string `xml:"operator,attr,omitempty"`
Val string `xml:"val,attr,omitempty"`
}
// xlsxFilters directly maps the filters (Filter Criteria) element. When
// multiple values are chosen to filter by, or when a group of date values are
// chosen to filter by, this element groups those criteria together.
type xlsxFilters struct {
Blank bool `xml:"blank,attr,omitempty"`
CalendarType string `xml:"calendarType,attr,omitempty"`
Filter []*xlsxFilter `xml:"filter"`
DateGroupItem []*xlsxDateGroupItem `xml:"dateGroupItem"`
}
// xlsxFilter directly maps the filter element. This element expresses a filter
// criteria value.
type xlsxFilter struct {
Val string `xml:"val,attr,omitempty"`
}
// xlsxColorFilter directly maps the colorFilter element. This element specifies
// the color to filter by and whether to use the cell's fill or font color in
// the filter criteria. If the cell's font or fill color does not match the
// color specified in the criteria, the rows corresponding to those cells are
// hidden from view.
type xlsxColorFilter struct {
CellColor bool `xml:"cellColor,attr"`
DxfID int `xml:"dxfId,attr"`
}
// xlsxDynamicFilter directly maps the dynamicFilter element. This collection
// specifies dynamic filter criteria. These criteria are considered dynamic
// because they can change, either with the data itself (e.g., "above average")
// or with the current system date (e.g., show values for "today"). For any
// cells whose values do not meet the specified criteria, the corresponding rows
// shall be hidden from view when the filter is applied.
type xlsxDynamicFilter struct {
MaxValISO string `xml:"maxValIso,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Val float64 `xml:"val,attr,omitempty"`
ValISO string `xml:"valIso,attr,omitempty"`
}
// xlsxIconFilter directly maps the iconFilter element. This element specifies
// the icon set and particular icon within that set to filter by. For any cells
// whose icon does not match the specified criteria, the corresponding rows
// shall be hidden from view when the filter is applied.
type xlsxIconFilter struct {
IconID int `xml:"iconId,attr"`
IconSet string `xml:"iconSet,attr,omitempty"`
}
// xlsxTop10 directly maps the top10 element. This element specifies the top N
// (percent or number of items) to filter by.
type xlsxTop10 struct {
FilterVal float64 `xml:"filterVal,attr,omitempty"`
Percent bool `xml:"percent,attr,omitempty"`
Top bool `xml:"top,attr"`
Val float64 `xml:"val,attr,omitempty"`
}
// xlsxDateGroupItem directly maps the dateGroupItem element. This collection is
// used to express a group of dates or times which are used in an AutoFilter
// criteria. [Note: See parent element for an example. end note] Values are
// always written in the calendar type of the first date encountered in the
// filter range, so that all subsequent dates, even when formatted or
// represented by other calendar types, can be correctly compared for the
// purposes of filtering.
type xlsxDateGroupItem struct {
DateTimeGrouping string `xml:"dateTimeGrouping,attr,omitempty"`
Day int `xml:"day,attr,omitempty"`
Hour int `xml:"hour,attr,omitempty"`
Minute int `xml:"minute,attr,omitempty"`
Month int `xml:"month,attr,omitempty"`
Second int `xml:"second,attr,omitempty"`
Year int `xml:"year,attr,omitempty"`
}
// xlsxTableColumns directly maps the element representing the collection of all
// table columns for this table.
type xlsxTableColumns struct {
Count int `xml:"count,attr"`
TableColumn []*xlsxTableColumn `xml:"tableColumn"`
}
// xlsxTableColumn directly maps the element representing a single column for
// this table.
type xlsxTableColumn struct {
ID int `xml:"id,attr"`
UniqueName string `xml:"uniqueName,attr,omitempty"`
Name string `xml:"name,attr"`
TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"`
TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"`
QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
}
// xlsxTableStyleInfo directly maps the tableStyleInfo element. This element
// describes which style is used to display this table, and specifies which
// portions of the table have the style applied.
type xlsxTableStyleInfo struct {
Name string `xml:"name,attr,omitempty"`
ShowFirstColumn bool `xml:"showFirstColumn,attr"`
ShowLastColumn bool `xml:"showLastColumn,attr"`
ShowRowStripes bool `xml:"showRowStripes,attr"`
ShowColumnStripes bool `xml:"showColumnStripes,attr"`
}
// xlsxSingleXMLCells is a single cell table is generated from an XML mapping.
// These really just look like regular cells to the spreadsheet user, but shall
// be implemented as Tables "under the covers."
type xlsxSingleXMLCells struct {
XMLName xml.Name `xml:"singleXmlCells"`
SingleXmlCell []xlsxSingleXMLCell `xml:"singleXmlCell"`
}
// xlsxSingleXMLCell is a element represents the table properties for a single
// cell XML table.
type xlsxSingleXMLCell struct {
XMLName xml.Name `xml:"singleXmlCell"`
ID int `xml:"id,attr"`
R string `xml:"r,attr"`
ConnectionID int `xml:"connectionId,attr"`
XMLCellPr xlsxXMLCellPr `xml:"xmlCellPr"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// xlsxXMLCellPr is a element stores the XML properties for the cell of a single
// cell xml table.
type xlsxXMLCellPr struct {
XMLName xml.Name `xml:"xmlCellPr"`
ID int `xml:"id,attr"`
UniqueName string `xml:"uniqueName,attr,omitempty"`
XMLPr *xlsxInnerXML `xml:"xmlPr"`
ExtLst *xlsxInnerXML `xml:"extLst"`
}
// Table directly maps the format settings of the table.
type Table struct {
tID int
rID string
tableXML string
Range string
Name string
StyleName string
ShowColumnStripes bool
ShowFirstColumn bool
ShowHeaderRow *bool
ShowLastColumn bool
ShowRowStripes *bool
}
// AutoFilterOptions directly maps the auto filter settings.
type AutoFilterOptions struct {
Column string
Expression string
}
================================================
FILE: xmlTheme.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import "encoding/xml"
// xlsxTheme directly maps the theme element in the namespace
// http://schemas.openxmlformats.org/drawingml/2006/main
type xlsxTheme struct {
XMLName xml.Name `xml:"a:theme"`
XMLNSa string `xml:"xmlns:a,attr"`
XMLNSr string `xml:"xmlns:r,attr"`
Name string `xml:"name,attr"`
ThemeElements xlsxBaseStyles `xml:"a:themeElements"`
ObjectDefaults xlsxObjectDefaults `xml:"a:objectDefaults"`
ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"a:extraClrSchemeLst"`
CustClrLst *xlsxInnerXML `xml:"a:custClrLst"`
ExtLst *xlsxExtLst `xml:"a:extLst"`
}
// xlsxBaseStyles defines the theme elements for a theme, and is the workhorse
// of the theme. The bulk of the shared theme information that is used by a
// given document is defined here. Within this complex type is defined a color
// scheme, a font scheme, and a style matrix (format scheme) that defines
// different formatting options for different pieces of a document.
type xlsxBaseStyles struct {
ClrScheme xlsxColorScheme `xml:"a:clrScheme"`
FontScheme xlsxFontScheme `xml:"a:fontScheme"`
FmtScheme xlsxStyleMatrix `xml:"a:fmtScheme"`
ExtLst *xlsxExtLst `xml:"a:extLst"`
}
// xlsxCTColor holds the actual color values that are to be applied to a given
// diagram and how those colors are to be applied.
type xlsxCTColor struct {
ScrgbClr *xlsxInnerXML `xml:"a:scrgbClr"`
SrgbClr *attrValString `xml:"a:srgbClr"`
HslClr *xlsxInnerXML `xml:"a:hslClr"`
SysClr *xlsxSysClr `xml:"a:sysClr"`
SchemeClr *xlsxInnerXML `xml:"a:schemeClr"`
PrstClr *xlsxInnerXML `xml:"a:prstClr"`
}
// xlsxColorScheme defines a set of colors for the theme. The set of colors
// consists of twelve color slots that can each hold a color of choice.
type xlsxColorScheme struct {
Name string `xml:"name,attr"`
Dk1 xlsxCTColor `xml:"a:dk1"`
Lt1 xlsxCTColor `xml:"a:lt1"`
Dk2 xlsxCTColor `xml:"a:dk2"`
Lt2 xlsxCTColor `xml:"a:lt2"`
Accent1 xlsxCTColor `xml:"a:accent1"`
Accent2 xlsxCTColor `xml:"a:accent2"`
Accent3 xlsxCTColor `xml:"a:accent3"`
Accent4 xlsxCTColor `xml:"a:accent4"`
Accent5 xlsxCTColor `xml:"a:accent5"`
Accent6 xlsxCTColor `xml:"a:accent6"`
Hlink xlsxCTColor `xml:"a:hlink"`
FolHlink xlsxCTColor `xml:"a:folHlink"`
ExtLst *xlsxExtLst `xml:"a:extLst"`
}
// objectDefaults element allows for the definition of default shape, line,
// and textbox formatting properties. An application can use this information
// to format a shape (or text) initially on insertion into a document.
type xlsxObjectDefaults struct {
ObjectDefaults string `xml:",innerxml"`
}
// xlsxExtraClrSchemeLst element is a container for the list of extra color
// schemes present in a document.
type xlsxExtraClrSchemeLst struct {
ExtraClrSchemeLst string `xml:",innerxml"`
}
// xlsxCTSupplementalFont defines an additional font that is used for language
// specific fonts in themes. For example, one can specify a font that gets used
// only within the Japanese language context.
type xlsxCTSupplementalFont struct {
Script string `xml:"script,attr"`
Typeface string `xml:"typeface,attr"`
}
// xlsxFontCollection defines a major and minor font which is used in the font
// scheme. A font collection consists of a font definition for Latin, East
// Asian, and complex script. On top of these three definitions, one can also
// define a font for use in a specific language or languages.
type xlsxFontCollection struct {
Latin *xlsxCTTextFont `xml:"a:latin"`
Ea *xlsxCTTextFont `xml:"a:ea"`
Cs *xlsxCTTextFont `xml:"a:cs"`
Font []xlsxCTSupplementalFont `xml:"a:font"`
ExtLst *xlsxExtLst `xml:"a:extLst"`
}
// xlsxFontScheme element defines the font scheme within the theme. The font
// scheme consists of a pair of major and minor fonts for which to use in a
// document. The major font corresponds well with the heading areas of a
// document, and the minor font corresponds well with the normal text or
// paragraph areas.
type xlsxFontScheme struct {
Name string `xml:"name,attr"`
MajorFont xlsxFontCollection `xml:"a:majorFont"`
MinorFont xlsxFontCollection `xml:"a:minorFont"`
ExtLst *xlsxExtLst `xml:"a:extLst"`
}
// xlsxStyleMatrix defines a set of formatting options, which can be referenced
// by documents that apply a certain style to a given part of an object. For
// example, in a given shape, say a rectangle, one can reference a themed line
// style, themed effect, and themed fill that would be theme specific and
// change when the theme is changed.
type xlsxStyleMatrix struct {
Name string `xml:"name,attr,omitempty"`
FillStyleLst xlsxFillStyleLst `xml:"a:fillStyleLst"`
LnStyleLst xlsxLnStyleLst `xml:"a:lnStyleLst"`
EffectStyleLst xlsxEffectStyleLst `xml:"a:effectStyleLst"`
BgFillStyleLst xlsxBgFillStyleLst `xml:"a:bgFillStyleLst"`
}
// xlsxFillStyleLst element defines a set of three fill styles that are used
// within a theme. The three fill styles are arranged in order from subtle to
// moderate to intense.
type xlsxFillStyleLst struct {
FillStyleLst string `xml:",innerxml"`
}
// xlsxLnStyleLst element defines a list of three line styles for use within a
// theme. The three line styles are arranged in order from subtle to moderate
// to intense versions of lines. This list makes up part of the style matrix.
type xlsxLnStyleLst struct {
LnStyleLst string `xml:",innerxml"`
}
// xlsxEffectStyleLst element defines a set of three effect styles that create
// the effect style list for a theme. The effect styles are arranged in order
// of subtle to moderate to intense.
type xlsxEffectStyleLst struct {
EffectStyleLst string `xml:",innerxml"`
}
// xlsxBgFillStyleLst element defines a list of background fills that are
// used within a theme. The background fills consist of three fills, arranged
// in order from subtle to moderate to intense.
type xlsxBgFillStyleLst struct {
BgFillStyleLst string `xml:",innerxml"`
}
// xlsxSysClr element specifies a color bound to predefined operating system
// elements.
type xlsxSysClr struct {
Val string `xml:"val,attr"`
LastClr string `xml:"lastClr,attr"`
}
// decodeTheme defines the structure used to parse the a:theme element for the
// theme.
type decodeTheme struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"`
Name string `xml:"name,attr"`
ThemeElements decodeBaseStyles `xml:"themeElements"`
ObjectDefaults xlsxObjectDefaults `xml:"objectDefaults"`
ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"`
CustClrLst *xlsxInnerXML `xml:"custClrLst"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeBaseStyles defines the structure used to parse the theme elements for a
// theme, and is the workhorse of the theme.
type decodeBaseStyles struct {
ClrScheme decodeColorScheme `xml:"clrScheme"`
FontScheme decodeFontScheme `xml:"fontScheme"`
FmtScheme decodeStyleMatrix `xml:"fmtScheme"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeColorScheme defines the structure used to parse a set of colors for the
// theme.
type decodeColorScheme struct {
Name string `xml:"name,attr"`
Dk1 decodeCTColor `xml:"dk1"`
Lt1 decodeCTColor `xml:"lt1"`
Dk2 decodeCTColor `xml:"dk2"`
Lt2 decodeCTColor `xml:"lt2"`
Accent1 decodeCTColor `xml:"accent1"`
Accent2 decodeCTColor `xml:"accent2"`
Accent3 decodeCTColor `xml:"accent3"`
Accent4 decodeCTColor `xml:"accent4"`
Accent5 decodeCTColor `xml:"accent5"`
Accent6 decodeCTColor `xml:"accent6"`
Hlink decodeCTColor `xml:"hlink"`
FolHlink decodeCTColor `xml:"folHlink"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeFontScheme defines the structure used to parse font scheme within the
// theme.
type decodeFontScheme struct {
Name string `xml:"name,attr"`
MajorFont decodeFontCollection `xml:"majorFont"`
MinorFont decodeFontCollection `xml:"minorFont"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeFontCollection defines the structure used to parse a major and minor
// font which is used in the font scheme.
type decodeFontCollection struct {
Latin *xlsxCTTextFont `xml:"latin"`
Ea *xlsxCTTextFont `xml:"ea"`
Cs *xlsxCTTextFont `xml:"cs"`
Font []xlsxCTSupplementalFont `xml:"font"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeCTColor defines the structure used to parse the actual color values
// that are to be applied to a given diagram and how those colors are to be
// applied.
type decodeCTColor struct {
ScrgbClr *xlsxInnerXML `xml:"scrgbClr"`
SrgbClr *attrValString `xml:"srgbClr"`
HslClr *xlsxInnerXML `xml:"hslClr"`
SysClr *xlsxSysClr `xml:"sysClr"`
SchemeClr *xlsxInnerXML `xml:"schemeClr"`
PrstClr *xlsxInnerXML `xml:"prstClr"`
}
// decodeStyleMatrix defines the structure used to parse a set of formatting
// options, which can be referenced by documents that apply a certain style to
// a given part of an object.
type decodeStyleMatrix struct {
Name string `xml:"name,attr,omitempty"`
FillStyleLst xlsxFillStyleLst `xml:"fillStyleLst"`
LnStyleLst xlsxLnStyleLst `xml:"lnStyleLst"`
EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"`
BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"`
}
================================================
FILE: xmlWorkbook.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxRelationships describe references from parts to other internal resources
// in the package or to external resources.
type xlsxRelationships struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"`
Relationships []xlsxRelationship `xml:"Relationship"`
}
// xlsxRelationship contains relations which maps id and XML.
type xlsxRelationship struct {
ID string `xml:"Id,attr"`
Target string `xml:",attr"`
Type string `xml:",attr"`
TargetMode string `xml:",attr,omitempty"`
}
// xlsxWorkbook contains elements and attributes that encompass the data
// content of the workbook. The workbook's child elements each have their own
// subclause references.
type xlsxWorkbook struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"`
Conformance string `xml:"conformance,attr,omitempty"`
FileVersion *xlsxFileVersion `xml:"fileVersion"`
FileSharing *xlsxExtLst `xml:"fileSharing"`
WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"`
AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"`
DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"`
BookViews *xlsxBookViews `xml:"bookViews"`
Sheets xlsxSheets `xml:"sheets"`
FunctionGroups *xlsxFunctionGroups `xml:"functionGroups"`
ExternalReferences *xlsxExternalReferences `xml:"externalReferences"`
DefinedNames *xlsxDefinedNames `xml:"definedNames"`
CalcPr *xlsxCalcPr `xml:"calcPr"`
OleSize *xlsxExtLst `xml:"oleSize"`
CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"`
PivotCaches *xlsxPivotCaches `xml:"pivotCaches"`
SmartTagPr *xlsxExtLst `xml:"smartTagPr"`
SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"`
WebPublishing *xlsxExtLst `xml:"webPublishing"`
FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"`
WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxFileRecoveryPr maps sheet recovery information. This element defines
// properties that track the state of the workbook file, such as whether the
// file was saved during a crash, or whether it should be opened in auto-recover
// mode.
type xlsxFileRecoveryPr struct {
AutoRecover bool `xml:"autoRecover,attr,omitempty"`
CrashSave bool `xml:"crashSave,attr,omitempty"`
DataExtractLoad bool `xml:"dataExtractLoad,attr,omitempty"`
RepairLoad bool `xml:"repairLoad,attr,omitempty"`
}
// xlsxWorkbookProtection directly maps the workbookProtection element. This
// element specifies options for protecting data in the workbook. Applications
// might use workbook protection to prevent anyone from accidentally changing,
// moving, or deleting important data. This protection can be ignored by
// applications which choose not to support this optional protection mechanism.
// When a password is to be hashed and stored in this element, it shall be
// hashed as defined below, starting from a UTF-16LE encoded string value. If
// there is a leading BOM character (U+FEFF) in the encoded password it is
// removed before hash calculation.
type xlsxWorkbookProtection struct {
LockRevision bool `xml:"lockRevision,attr,omitempty"`
LockStructure bool `xml:"lockStructure,attr,omitempty"`
LockWindows bool `xml:"lockWindows,attr,omitempty"`
RevisionsAlgorithmName string `xml:"revisionsAlgorithmName,attr,omitempty"`
RevisionsHashValue string `xml:"revisionsHashValue,attr,omitempty"`
RevisionsSaltValue string `xml:"revisionsSaltValue,attr,omitempty"`
RevisionsSpinCount int `xml:"revisionsSpinCount,attr,omitempty"`
WorkbookAlgorithmName string `xml:"workbookAlgorithmName,attr,omitempty"`
WorkbookHashValue string `xml:"workbookHashValue,attr,omitempty"`
WorkbookSaltValue string `xml:"workbookSaltValue,attr,omitempty"`
WorkbookSpinCount int `xml:"workbookSpinCount,attr,omitempty"`
}
// xlsxFileVersion directly maps the fileVersion element. This element defines
// properties that track which version of the application accessed the data and
// source code contained in the file.
type xlsxFileVersion struct {
AppName string `xml:"appName,attr,omitempty"`
CodeName string `xml:"codeName,attr,omitempty"`
LastEdited string `xml:"lastEdited,attr,omitempty"`
LowestEdited string `xml:"lowestEdited,attr,omitempty"`
RupBuild string `xml:"rupBuild,attr,omitempty"`
}
// xlsxWorkbookPr directly maps the workbookPr element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// defines a collection of workbook properties.
type xlsxWorkbookPr struct {
Date1904 bool `xml:"date1904,attr,omitempty"`
ShowObjects string `xml:"showObjects,attr,omitempty"`
ShowBorderUnselectedTables *bool `xml:"showBorderUnselectedTables,attr"`
FilterPrivacy bool `xml:"filterPrivacy,attr,omitempty"`
PromptedSolutions bool `xml:"promptedSolutions,attr,omitempty"`
ShowInkAnnotation *bool `xml:"showInkAnnotation,attr"`
BackupFile bool `xml:"backupFile,attr,omitempty"`
SaveExternalLinkValues *bool `xml:"saveExternalLinkValues,attr"`
UpdateLinks string `xml:"updateLinks,attr,omitempty"`
CodeName string `xml:"codeName,attr,omitempty"`
HidePivotFieldList bool `xml:"hidePivotFieldList,attr,omitempty"`
ShowPivotChartFilter bool `xml:"showPivotChartFilter,attr,omitempty"`
AllowRefreshQuery bool `xml:"allowRefreshQuery,attr,omitempty"`
PublishItems bool `xml:"publishItems,attr,omitempty"`
CheckCompatibility bool `xml:"checkCompatibility,attr,omitempty"`
AutoCompressPictures *bool `xml:"autoCompressPictures,attr"`
RefreshAllConnections bool `xml:"refreshAllConnections,attr,omitempty"`
DefaultThemeVersion string `xml:"defaultThemeVersion,attr,omitempty"`
}
// xlsxBookViews directly maps the bookViews element. This element specifies the
// collection of workbook views of the enclosing workbook. Each view can specify
// a window position, filter options, and other configurations. There is no
// limit on the number of workbook views that can be defined for a workbook.
type xlsxBookViews struct {
WorkBookView []xlsxWorkBookView `xml:"workbookView"`
}
// xlsxWorkBookView directly maps the workbookView element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// specifies a single Workbook view.
type xlsxWorkBookView struct {
Visibility string `xml:"visibility,attr,omitempty"`
Minimized bool `xml:"minimized,attr,omitempty"`
ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"`
ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"`
ShowSheetTabs *bool `xml:"showSheetTabs,attr"`
XWindow string `xml:"xWindow,attr,omitempty"`
YWindow string `xml:"yWindow,attr,omitempty"`
WindowWidth int `xml:"windowWidth,attr,omitempty"`
WindowHeight int `xml:"windowHeight,attr,omitempty"`
TabRatio float64 `xml:"tabRatio,attr,omitempty"`
FirstSheet int `xml:"firstSheet,attr,omitempty"`
ActiveTab int `xml:"activeTab,attr,omitempty"`
AutoFilterDateGrouping *bool `xml:"autoFilterDateGrouping,attr"`
}
// xlsxSheets directly maps the sheets element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
type xlsxSheets struct {
Sheet []xlsxSheet `xml:"sheet"`
}
// xlsxSheet defines a sheet in this workbook. Sheet data is stored in a
// separate part.
type xlsxSheet struct {
Name string `xml:"name,attr,omitempty"`
SheetID int `xml:"sheetId,attr,omitempty"`
ID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr"`
State string `xml:"state,attr,omitempty"`
}
// xlsxFunctionGroup represents a single function group.
type xlsxFunctionGroup struct {
Name string `xml:"name,attr"`
}
// xlsxFunctionGroups defines the collection of function groups for the workbook.
type xlsxFunctionGroups struct {
BuiltInGroupCount *int `xml:"builtInGroupCount,attr"`
FunctionGroup []xlsxFunctionGroup `xml:"functionGroup"`
}
// xlsxExternalReferences directly maps the externalReferences element of the
// external workbook references part.
type xlsxExternalReferences struct {
ExternalReference []xlsxExternalReference `xml:"externalReference"`
}
// xlsxExternalReference directly maps the externalReference element of the
// external workbook references part.
type xlsxExternalReference struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxPivotCaches element enumerates pivot cache definition parts used by pivot
// tables and formulas in this workbook.
type xlsxPivotCaches struct {
PivotCache []xlsxPivotCache `xml:"pivotCache"`
}
// xlsxPivotCache directly maps the pivotCache element.
type xlsxPivotCache struct {
CacheID int `xml:"cacheId,attr"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// extLst element provides a convention for extending spreadsheetML in
// predefined locations. The locations shall be denoted with the extLst element,
// and are called extension lists. Extension list locations within the markup
// document are specified in the markup specification and can be used to store
// extensions to the markup specification, whether those are future version
// extensions of the markup specification or are private extensions implemented
// independently of the markup specification. Markup within an extension might
// not be understood by a consumer.
type xlsxExtLst struct {
Ext string `xml:",innerxml"`
}
// xlsxExt represents a the future feature data storage area. Each extension
// within an extension list shall be contained within an ext element.
// Extensions shall be versioned by namespace, using the uri attribute, and
// shall be allowed to appear in any order within the extension list. Any
// number of extensions shall be allowed within an extension list.
type xlsxExt struct {
XMLName xml.Name `xml:"ext"`
URI string `xml:"uri,attr"`
Content string `xml:",innerxml"`
xmlns []xml.Attr
}
// xlsxAlternateContent is a container for a sequence of multiple
// representations of a given piece of content. The program reading the file
// should only process one of these, and the one chosen should be based on
// which conditions match.
type xlsxAlternateContent struct {
XMLNSMC string `xml:"xmlns:mc,attr,omitempty"`
Content string `xml:",innerxml"`
}
// xlsxChoice element shall be an element in the Markup Compatibility namespace
// with local name "Choice". Parent elements of Choice elements shall be
// AlternateContent elements.
type xlsxChoice struct {
XMLName xml.Name `xml:"mc:Choice"`
XMLNSA14 string `xml:"xmlns:a14,attr,omitempty"`
XMLNSSle15 string `xml:"xmlns:sle15,attr,omitempty"`
Requires string `xml:"Requires,attr,omitempty"`
Content string `xml:",innerxml"`
}
// xlsxFallback element shall be an element in the Markup Compatibility
// namespace with local name "Fallback". Parent elements of Fallback elements
// shall be AlternateContent elements.
type xlsxFallback struct {
XMLName xml.Name `xml:"mc:Fallback"`
Content string `xml:",innerxml"`
}
// xlsxInnerXML holds parts of XML content currently not unmarshal.
type xlsxInnerXML struct {
Content string `xml:",innerxml"`
}
// decodeExtLst defines the structure used to parse the extLst element
// of the future feature data storage area.
type decodeExtLst struct {
XMLName xml.Name `xml:"extLst"`
Ext []*xlsxExt `xml:"ext"`
}
// decodeExt defines the structure used to parse the ext element.
type decodeExt struct {
URI string `xml:"uri,attr,omitempty"`
Content string `xml:",innerxml"`
}
// xlsxDefinedNames directly maps the definedNames element. This element defines
// the collection of defined names for this workbook. Defined names are
// descriptive names to represent cells, ranges of cells, formulas, or constant
// values. Defined names can be used to represent a range on any worksheet.
type xlsxDefinedNames struct {
DefinedName []xlsxDefinedName `xml:"definedName"`
}
// xlsxDefinedName directly maps the definedName element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// defines a defined name within this workbook. A defined name is descriptive
// text that is used to represent a cell, range of cells, formula, or constant
// value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
type xlsxDefinedName struct {
Comment string `xml:"comment,attr,omitempty"`
CustomMenu string `xml:"customMenu,attr,omitempty"`
Description string `xml:"description,attr,omitempty"`
Function bool `xml:"function,attr,omitempty"`
FunctionGroupID int `xml:"functionGroupId,attr,omitempty"`
Help string `xml:"help,attr,omitempty"`
Hidden bool `xml:"hidden,attr,omitempty"`
LocalSheetID *int `xml:"localSheetId,attr"`
Name string `xml:"name,attr,omitempty"`
PublishToServer bool `xml:"publishToServer,attr,omitempty"`
ShortcutKey string `xml:"shortcutKey,attr,omitempty"`
StatusBar string `xml:"statusBar,attr,omitempty"`
VbProcedure bool `xml:"vbProcedure,attr,omitempty"`
WorkbookParameter bool `xml:"workbookParameter,attr,omitempty"`
Xlm bool `xml:"xml,attr,omitempty"`
Data string `xml:",chardata"`
}
// xlsxCalcPr directly maps the calcPr element. This element defines the
// collection of properties the application uses to record calculation status
// and details. Calculation is the process of computing formulas and then
// displaying the results as values in the cells that contain the formulas.
type xlsxCalcPr struct {
CalcCompleted bool `xml:"calcCompleted,attr,omitempty"`
CalcID int `xml:"calcId,attr,omitempty"`
CalcMode string `xml:"calcMode,attr,omitempty"`
CalcOnSave bool `xml:"calcOnSave,attr,omitempty"`
ConcurrentCalc *bool `xml:"concurrentCalc,attr"`
ConcurrentManualCount int `xml:"concurrentManualCount,attr,omitempty"`
ForceFullCalc bool `xml:"forceFullCalc,attr,omitempty"`
FullCalcOnLoad bool `xml:"fullCalcOnLoad,attr,omitempty"`
FullPrecision bool `xml:"fullPrecision,attr,omitempty"`
Iterate bool `xml:"iterate,attr,omitempty"`
IterateCount int `xml:"iterateCount,attr,omitempty"`
IterateDelta float64 `xml:"iterateDelta,attr,omitempty"`
RefMode string `xml:"refMode,attr,omitempty"`
}
// xlsxCustomWorkbookViews defines the collection of custom workbook views that
// are defined for this workbook. A customWorkbookView is similar in concept to
// a workbookView in that its attributes contain settings related to the way
// that the workbook should be displayed on a screen by a spreadsheet
// application.
type xlsxCustomWorkbookViews struct {
CustomWorkbookView []xlsxCustomWorkbookView `xml:"customWorkbookView"`
}
// xlsxCustomWorkbookView directly maps the customWorkbookView element. This
// element specifies a single custom workbook view. A custom workbook view
// consists of a set of display and print settings that you can name and apply
// to a workbook. You can create more than one custom workbook view of the same
// workbook. Custom Workbook Views are not required in order to construct a
// valid SpreadsheetML document, and are not necessary if the document is never
// displayed by a spreadsheet application, or if the spreadsheet application has
// a fixed display for workbooks. However, if a spreadsheet application chooses
// to implement configurable display modes, the customWorkbookView element
// should be used to persist the settings for those display modes.
type xlsxCustomWorkbookView struct {
ActiveSheetID *int `xml:"activeSheetId,attr"`
AutoUpdate *bool `xml:"autoUpdate,attr"`
ChangesSavedWin *bool `xml:"changesSavedWin,attr"`
GUID *string `xml:"guid,attr"`
IncludeHiddenRowCol *bool `xml:"includeHiddenRowCol,attr"`
IncludePrintSettings *bool `xml:"includePrintSettings,attr"`
Maximized *bool `xml:"maximized,attr"`
MergeInterval int `xml:"mergeInterval,attr"`
Minimized *bool `xml:"minimized,attr"`
Name *string `xml:"name,attr"`
OnlySync *bool `xml:"onlySync,attr"`
PersonalView *bool `xml:"personalView,attr"`
ShowComments *string `xml:"showComments,attr"`
ShowFormulaBar *bool `xml:"showFormulaBar,attr"`
ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"`
ShowObjects *string `xml:"showObjects,attr"`
ShowSheetTabs *bool `xml:"showSheetTabs,attr"`
ShowStatusbar *bool `xml:"showStatusbar,attr"`
ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"`
TabRatio *float64 `xml:"tabRatio,attr"`
WindowHeight *int `xml:"windowHeight,attr"`
WindowWidth *int `xml:"windowWidth,attr"`
XWindow *int `xml:"xWindow,attr"`
YWindow *int `xml:"yWindow,attr"`
}
// DefinedName directly maps the name for a cell or cell range on a
// worksheet.
type DefinedName struct {
Name string
Comment string
RefersTo string
Scope string
}
// CalcPropsOptions defines the collection of properties the application uses to
// record calculation status and details.
type CalcPropsOptions struct {
CalcID *uint
CalcMode *string
FullCalcOnLoad *bool
RefMode *string
Iterate *bool
IterateCount *uint
IterateDelta *float64
FullPrecision *bool
CalcCompleted *bool
CalcOnSave *bool
ConcurrentCalc *bool
ConcurrentManualCount *uint
ForceFullCalc *bool
}
// WorkbookPropsOptions directly maps the settings of workbook proprieties.
type WorkbookPropsOptions struct {
Date1904 *bool
FilterPrivacy *bool
CodeName *string
}
// WorkbookProtectionOptions directly maps the settings of workbook protection.
type WorkbookProtectionOptions struct {
AlgorithmName string
Password string
LockStructure bool
LockWindows bool
}
================================================
FILE: xmlWorksheet.go
================================================
// Copyright 2016 - 2026 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.25.0 or later.
package excelize
import (
"encoding/xml"
"sync"
)
// xlsxWorksheet directly maps the worksheet element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
type xlsxWorksheet struct {
mu sync.Mutex
formulaSI sync.Map
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
SheetPr *xlsxSheetPr `xml:"sheetPr"`
Dimension *xlsxDimension `xml:"dimension"`
SheetViews *xlsxSheetViews `xml:"sheetViews"`
SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"`
Cols *xlsxCols `xml:"cols"`
SheetData xlsxSheetData `xml:"sheetData"`
SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"`
SheetProtection *xlsxSheetProtection `xml:"sheetProtection"`
ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"`
Scenarios *xlsxInnerXML `xml:"scenarios"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
SortState *xlsxSortState `xml:"sortState"`
DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"`
CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"`
MergeCells *xlsxMergeCells `xml:"mergeCells"`
PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"`
DataValidations *xlsxDataValidations `xml:"dataValidations"`
Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"`
PrintOptions *xlsxPrintOptions `xml:"printOptions"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PageSetUp *xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
RowBreaks *xlsxRowBreaks `xml:"rowBreaks"`
ColBreaks *xlsxColBreaks `xml:"colBreaks"`
CustomProperties *xlsxInnerXML `xml:"customProperties"`
CellWatches *xlsxInnerXML `xml:"cellWatches"`
IgnoredErrors *xlsxIgnoredErrors `xml:"ignoredErrors"`
SmartTags *xlsxInnerXML `xml:"smartTags"`
Drawing *xlsxDrawing `xml:"drawing"`
LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"`
LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"`
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
Picture *xlsxPicture `xml:"picture"`
OleObjects *xlsxInnerXML `xml:"oleObjects"`
Controls *xlsxInnerXML `xml:"controls"`
WebPublishItems *xlsxInnerXML `xml:"webPublishItems"`
AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"`
TableParts *xlsxTableParts `xml:"tableParts"`
ExtLst *xlsxExtLst `xml:"extLst"`
DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
}
// xlsxDrawing change r:id to rid in the namespace.
type xlsxDrawing struct {
XMLName xml.Name `xml:"drawing"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxHeaderFooter directly maps the headerFooter element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - When printed or
// viewed in page layout view (§18.18.69), each page of a worksheet can have a
// page header, a page footer, or both. The headers and footers on odd-numbered
// pages can differ from those on even-numbered pages, and the headers and
// footers on the first page can differ from those on odd- and even-numbered
// pages. In the latter case, the first page is not considered an odd page.
type xlsxHeaderFooter struct {
XMLName xml.Name `xml:"headerFooter"`
DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"`
DifferentFirst bool `xml:"differentFirst,attr,omitempty"`
ScaleWithDoc *bool `xml:"scaleWithDoc,attr"`
AlignWithMargins *bool `xml:"alignWithMargins,attr"`
OddHeader string `xml:"oddHeader,omitempty"`
OddFooter string `xml:"oddFooter,omitempty"`
EvenHeader string `xml:"evenHeader,omitempty"`
EvenFooter string `xml:"evenFooter,omitempty"`
FirstHeader string `xml:"firstHeader,omitempty"`
FirstFooter string `xml:"firstFooter,omitempty"`
}
// xlsxDrawingHF (Drawing Reference in Header Footer) specifies the usage of
// drawing objects to be rendered in the headers and footers of the sheet. It
// specifies an explicit relationship to the part containing the DrawingML
// shapes used in the headers and footers. It also indicates where in the
// headers and footers each shape belongs. One drawing object can appear in
// each of the left section, center section and right section of a header and
// a footer.
type xlsxDrawingHF struct {
Content string `xml:",innerxml"`
}
// xlsxPageSetUp directly maps the pageSetup element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup
// settings for the worksheet.
type xlsxPageSetUp struct {
XMLName xml.Name `xml:"pageSetup"`
BlackAndWhite bool `xml:"blackAndWhite,attr,omitempty"`
CellComments string `xml:"cellComments,attr,omitempty"`
Copies int `xml:"copies,attr,omitempty"`
Draft bool `xml:"draft,attr,omitempty"`
Errors string `xml:"errors,attr,omitempty"`
FirstPageNumber string `xml:"firstPageNumber,attr,omitempty"`
FitToHeight *int `xml:"fitToHeight,attr"`
FitToWidth *int `xml:"fitToWidth,attr"`
HorizontalDPI string `xml:"horizontalDpi,attr,omitempty"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Orientation string `xml:"orientation,attr,omitempty"`
PageOrder string `xml:"pageOrder,attr,omitempty"`
PaperHeight string `xml:"paperHeight,attr,omitempty"`
PaperSize *int `xml:"paperSize,attr"`
PaperWidth string `xml:"paperWidth,attr,omitempty"`
Scale int `xml:"scale,attr,omitempty"`
UseFirstPageNumber bool `xml:"useFirstPageNumber,attr,omitempty"`
UsePrinterDefaults bool `xml:"usePrinterDefaults,attr,omitempty"`
VerticalDPI string `xml:"verticalDpi,attr,omitempty"`
}
// xlsxPrintOptions directly maps the printOptions element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Print options for
// the sheet. Printer-specific settings are stored separately in the Printer
// Settings part.
type xlsxPrintOptions struct {
XMLName xml.Name `xml:"printOptions"`
GridLines bool `xml:"gridLines,attr,omitempty"`
GridLinesSet bool `xml:"gridLinesSet,attr,omitempty"`
Headings bool `xml:"headings,attr,omitempty"`
HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"`
VerticalCentered bool `xml:"verticalCentered,attr,omitempty"`
}
// xlsxPageMargins directly maps the pageMargins element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page margins for
// a sheet or a custom sheet view.
type xlsxPageMargins struct {
XMLName xml.Name `xml:"pageMargins"`
Left float64 `xml:"left,attr"`
Right float64 `xml:"right,attr"`
Top float64 `xml:"top,attr"`
Bottom float64 `xml:"bottom,attr"`
Header float64 `xml:"header,attr"`
Footer float64 `xml:"footer,attr"`
}
// xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. This element
// specifies the sheet formatting properties.
type xlsxSheetFormatPr struct {
XMLName xml.Name `xml:"sheetFormatPr"`
BaseColWidth uint8 `xml:"baseColWidth,attr,omitempty"`
DefaultColWidth float64 `xml:"defaultColWidth,attr,omitempty"`
DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
CustomHeight bool `xml:"customHeight,attr,omitempty"`
ZeroHeight bool `xml:"zeroHeight,attr,omitempty"`
ThickTop bool `xml:"thickTop,attr,omitempty"`
ThickBottom bool `xml:"thickBottom,attr,omitempty"`
OutlineLevelRow uint8 `xml:"outlineLevelRow,attr,omitempty"`
OutlineLevelCol uint8 `xml:"outlineLevelCol,attr,omitempty"`
}
// xlsxSheetViews represents worksheet views collection.
type xlsxSheetViews struct {
XMLName xml.Name `xml:"sheetViews"`
SheetView []xlsxSheetView `xml:"sheetView"`
}
// xlsxSheetView represents a single sheet view definition. When more than one
// sheet view is defined in the file, it means that when opening the workbook,
// each sheet view corresponds to a separate window within the spreadsheet
// application, where each window is showing the particular sheet containing
// the same workbookViewId value, the last sheetView definition is loaded, and
// the others are discarded. When multiple windows are viewing the same sheet,
// multiple sheetView elements (with corresponding workbookView entries) are
// saved.
type xlsxSheetView struct {
WindowProtection bool `xml:"windowProtection,attr,omitempty"`
ShowFormulas bool `xml:"showFormulas,attr,omitempty"`
ShowGridLines *bool `xml:"showGridLines,attr"`
ShowRowColHeaders *bool `xml:"showRowColHeaders,attr"`
ShowZeros *bool `xml:"showZeros,attr,omitempty"`
RightToLeft bool `xml:"rightToLeft,attr,omitempty"`
TabSelected bool `xml:"tabSelected,attr,omitempty"`
ShowRuler *bool `xml:"showRuler,attr,omitempty"`
ShowWhiteSpace *bool `xml:"showWhiteSpace,attr"`
ShowOutlineSymbols bool `xml:"showOutlineSymbols,attr,omitempty"`
DefaultGridColor *bool `xml:"defaultGridColor,attr"`
View string `xml:"view,attr,omitempty"`
TopLeftCell string `xml:"topLeftCell,attr,omitempty"`
ColorID int `xml:"colorId,attr,omitempty"`
ZoomScale float64 `xml:"zoomScale,attr,omitempty"`
ZoomScaleNormal float64 `xml:"zoomScaleNormal,attr,omitempty"`
ZoomScalePageLayoutView float64 `xml:"zoomScalePageLayoutView,attr,omitempty"`
ZoomScaleSheetLayoutView float64 `xml:"zoomScaleSheetLayoutView,attr,omitempty"`
WorkbookViewID int `xml:"workbookViewId,attr"`
Pane *xlsxPane `xml:"pane,omitempty"`
Selection []*xlsxSelection `xml:"selection"`
}
// xlsxSelection directly maps the selection element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet view
// selection.
type xlsxSelection struct {
ActiveCell string `xml:"activeCell,attr,omitempty"`
ActiveCellID *int `xml:"activeCellId,attr"`
Pane string `xml:"pane,attr,omitempty"`
SQRef string `xml:"sqref,attr,omitempty"`
}
// xlsxSelection directly maps the selection element. Worksheet view pane.
type xlsxPane struct {
ActivePane string `xml:"activePane,attr,omitempty"`
State string `xml:"state,attr,omitempty"` // Either "split" or "frozen"
TopLeftCell string `xml:"topLeftCell,attr,omitempty"`
XSplit float64 `xml:"xSplit,attr,omitempty"`
YSplit float64 `xml:"ySplit,attr,omitempty"`
}
// xlsxSheetPr directly maps the sheetPr element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Sheet-level
// properties.
type xlsxSheetPr struct {
XMLName xml.Name `xml:"sheetPr"`
SyncHorizontal bool `xml:"syncHorizontal,attr,omitempty"`
SyncVertical bool `xml:"syncVertical,attr,omitempty"`
SyncRef string `xml:"syncRef,attr,omitempty"`
TransitionEvaluation bool `xml:"transitionEvaluation,attr,omitempty"`
TransitionEntry bool `xml:"transitionEntry,attr,omitempty"`
Published *bool `xml:"published,attr"`
CodeName string `xml:"codeName,attr,omitempty"`
FilterMode bool `xml:"filterMode,attr,omitempty"`
EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"`
TabColor *xlsxColor `xml:"tabColor"`
OutlinePr *xlsxOutlinePr `xml:"outlinePr"`
PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr"`
}
// xlsxOutlinePr maps to the outlinePr element. SummaryBelow allows you to
// adjust the direction of grouper controls.
type xlsxOutlinePr struct {
ApplyStyles *bool `xml:"applyStyles,attr"`
SummaryBelow *bool `xml:"summaryBelow,attr"`
SummaryRight *bool `xml:"summaryRight,attr"`
ShowOutlineSymbols *bool `xml:"showOutlineSymbols,attr"`
}
// xlsxPageSetUpPr expresses page setup properties of the worksheet.
type xlsxPageSetUpPr struct {
AutoPageBreaks bool `xml:"autoPageBreaks,attr,omitempty"`
FitToPage bool `xml:"fitToPage,attr,omitempty"`
}
// xlsxCols defines column width and column formatting for one or more columns
// of the worksheet.
type xlsxCols struct {
XMLName xml.Name `xml:"cols"`
Col []xlsxCol `xml:"col"`
}
// xlsxCol directly maps the col (Column Width & Formatting). Defines column
// width and column formatting for one or more columns of the worksheet.
type xlsxCol struct {
BestFit bool `xml:"bestFit,attr,omitempty"`
Collapsed bool `xml:"collapsed,attr,omitempty"`
CustomWidth bool `xml:"customWidth,attr,omitempty"`
Hidden bool `xml:"hidden,attr,omitempty"`
Max int `xml:"max,attr"`
Min int `xml:"min,attr"`
OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"`
Phonetic bool `xml:"phonetic,attr,omitempty"`
Style int `xml:"style,attr,omitempty"`
Width *float64 `xml:"width,attr"`
}
// xlsxDimension directly maps the dimension element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - This element
// specifies the used range of the worksheet. It specifies the row and column
// bounds of used cells in the worksheet. This is optional and is not
// required. Used cells include cells with formulas, text content, and cell
// formatting. When an entire column is formatted, only the first cell in that
// column is considered used.
type xlsxDimension struct {
XMLName xml.Name `xml:"dimension"`
Ref string `xml:"ref,attr"`
}
// xlsxSheetData collection represents the cell table itself. This collection
// expresses information about each cell, grouped together by rows in the
// worksheet.
type xlsxSheetData struct {
XMLName xml.Name `xml:"sheetData"`
Row []xlsxRow `xml:"row"`
}
// xlsxRow directly maps the row element. The element expresses information
// about an entire row of a worksheet, and contains all cell definitions for a
// particular row in the worksheet.
type xlsxRow struct {
C []xlsxC `xml:"c"`
R int `xml:"r,attr,omitempty"`
Spans string `xml:"spans,attr,omitempty"`
S int `xml:"s,attr,omitempty"`
CustomFormat bool `xml:"customFormat,attr,omitempty"`
Ht *float64 `xml:"ht,attr"`
Hidden bool `xml:"hidden,attr,omitempty"`
CustomHeight bool `xml:"customHeight,attr,omitempty"`
OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"`
Collapsed bool `xml:"collapsed,attr,omitempty"`
ThickTop bool `xml:"thickTop,attr,omitempty"`
ThickBot bool `xml:"thickBot,attr,omitempty"`
Ph bool `xml:"ph,attr,omitempty"`
}
// xlsxSortState directly maps the sortState element. This collection
// preserves the AutoFilter sort state.
type xlsxSortState struct {
ColumnSort bool `xml:"columnSort,attr,omitempty"`
CaseSensitive bool `xml:"caseSensitive,attr,omitempty"`
SortMethod string `xml:"sortMethod,attr,omitempty"`
Ref string `xml:"ref,attr"`
Content string `xml:",innerxml"`
}
// xlsxCustomSheetViews directly maps the customSheetViews element. This is a
// collection of custom sheet views.
type xlsxCustomSheetViews struct {
XMLName xml.Name `xml:"customSheetViews"`
CustomSheetView []*xlsxCustomSheetView `xml:"customSheetView"`
}
// xlsxBrk directly maps the row or column break to use when paginating a
// worksheet.
type xlsxBrk struct {
ID int `xml:"id,attr,omitempty"`
Min int `xml:"min,attr,omitempty"`
Max int `xml:"max,attr,omitempty"`
Man bool `xml:"man,attr,omitempty"`
Pt bool `xml:"pt,attr,omitempty"`
}
// xlsxRowBreaks directly maps a collection of the row breaks.
type xlsxRowBreaks struct {
XMLName xml.Name `xml:"rowBreaks"`
xlsxBreaks
}
// xlsxRowBreaks directly maps a collection of the column breaks.
type xlsxColBreaks struct {
XMLName xml.Name `xml:"colBreaks"`
xlsxBreaks
}
// xlsxBreaks directly maps a collection of the row or column breaks.
type xlsxBreaks struct {
Brk []*xlsxBrk `xml:"brk"`
Count int `xml:"count,attr,omitempty"`
ManualBreakCount int `xml:"manualBreakCount,attr,omitempty"`
}
// xlsxCustomSheetView directly maps the customSheetView element.
type xlsxCustomSheetView struct {
Pane *xlsxPane `xml:"pane"`
Selection *xlsxSelection `xml:"selection"`
RowBreaks *xlsxBreaks `xml:"rowBreaks"`
ColBreaks *xlsxBreaks `xml:"colBreaks"`
PageMargins *xlsxPageMargins `xml:"pageMargins"`
PrintOptions *xlsxPrintOptions `xml:"printOptions"`
PageSetup *xlsxPageSetUp `xml:"pageSetup"`
HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
ExtLst *xlsxExtLst `xml:"extLst"`
GUID string `xml:"guid,attr"`
Scale int `xml:"scale,attr,omitempty"`
ColorID int `xml:"colorId,attr,omitempty"`
ShowPageBreaks bool `xml:"showPageBreaks,attr,omitempty"`
ShowFormulas bool `xml:"showFormulas,attr,omitempty"`
ShowGridLines bool `xml:"showGridLines,attr,omitempty"`
ShowRowCol bool `xml:"showRowCol,attr,omitempty"`
OutlineSymbols bool `xml:"outlineSymbols,attr,omitempty"`
ZeroValues bool `xml:"zeroValues,attr,omitempty"`
FitToPage bool `xml:"fitToPage,attr,omitempty"`
PrintArea bool `xml:"printArea,attr,omitempty"`
Filter bool `xml:"filter,attr,omitempty"`
ShowAutoFilter bool `xml:"showAutoFilter,attr,omitempty"`
HiddenRows bool `xml:"hiddenRows,attr,omitempty"`
HiddenColumns bool `xml:"hiddenColumns,attr,omitempty"`
State string `xml:"state,attr,omitempty"`
FilterUnique bool `xml:"filterUnique,attr,omitempty"`
View string `xml:"view,attr,omitempty"`
ShowRuler bool `xml:"showRuler,attr,omitempty"`
TopLeftCell string `xml:"topLeftCell,attr,omitempty"`
}
// xlsxMergeCell directly maps the mergeCell element. A single merged cell.
type xlsxMergeCell struct {
Ref string `xml:"ref,attr,omitempty"`
rect []int
}
// xlsxMergeCells directly maps the mergeCells element. This collection
// expresses all the merged cells in the sheet.
type xlsxMergeCells struct {
XMLName xml.Name `xml:"mergeCells"`
Count int `xml:"count,attr,omitempty"`
Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"`
}
// xlsxDataValidations expresses all data validation information for cells in a
// sheet which have data validation features applied.
type xlsxDataValidations struct {
XMLName xml.Name `xml:"dataValidations"`
Count int `xml:"count,attr,omitempty"`
DisablePrompts bool `xml:"disablePrompts,attr,omitempty"`
XWindow int `xml:"xWindow,attr,omitempty"`
YWindow int `xml:"yWindow,attr,omitempty"`
DataValidation []*xlsxDataValidation `xml:"dataValidation"`
}
// xlsxDataValidation directly maps the single item of data validation defined
// on a range of the worksheet.
type xlsxDataValidation struct {
AllowBlank bool `xml:"allowBlank,attr"`
Error *string `xml:"error,attr"`
ErrorStyle *string `xml:"errorStyle,attr"`
ErrorTitle *string `xml:"errorTitle,attr"`
Operator string `xml:"operator,attr,omitempty"`
Prompt *string `xml:"prompt,attr"`
PromptTitle *string `xml:"promptTitle,attr"`
ShowDropDown bool `xml:"showDropDown,attr,omitempty"`
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
Sqref string `xml:"sqref,attr"`
XMSqref string `xml:"sqref,omitempty"`
Type string `xml:"type,attr,omitempty"`
Formula1 *xlsxInnerXML `xml:"formula1"`
Formula2 *xlsxInnerXML `xml:"formula2"`
}
// xlsxX14DataValidation directly maps the single item of data validation
// defined on a extLst element of the worksheet.
type xlsxX14DataValidation struct {
XMLName xml.Name `xml:"x14:dataValidation"`
AllowBlank bool `xml:"allowBlank,attr"`
Error *string `xml:"error,attr"`
ErrorStyle *string `xml:"errorStyle,attr"`
ErrorTitle *string `xml:"errorTitle,attr"`
Operator string `xml:"operator,attr,omitempty"`
Prompt *string `xml:"prompt,attr"`
PromptTitle *string `xml:"promptTitle,attr"`
ShowDropDown bool `xml:"showDropDown,attr,omitempty"`
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
Sqref string `xml:"sqref,attr"`
Type string `xml:"type,attr,omitempty"`
Formula1 *xlsxInnerXML `xml:"x14:formula1"`
Formula2 *xlsxInnerXML `xml:"x14:formula2"`
XMSqref string `xml:"xm:sqref,omitempty"`
}
// xlsxX14DataValidations expresses all data validation information for cells in
// a sheet extLst element which have data validation features applied.
type xlsxX14DataValidations struct {
XMLName xml.Name `xml:"x14:dataValidations"`
XMLNSXM string `xml:"xmlns:xm,attr,omitempty"`
Count int `xml:"count,attr,omitempty"`
DisablePrompts bool `xml:"disablePrompts,attr,omitempty"`
XWindow int `xml:"xWindow,attr,omitempty"`
YWindow int `xml:"yWindow,attr,omitempty"`
DataValidation []*xlsxX14DataValidation
}
// xlsxC collection represents a cell in the worksheet. Information about the
// cell's location (reference), value, data type, formatting, and formula is
// expressed here.
//
// This simple type is restricted to the values listed in the following table:
//
// Enumeration Value | Description
// ---------------------------+---------------------------------
// b (Boolean) | Cell containing a boolean.
// d (Date) | Cell contains a date in the ISO 8601 format.
// e (Error) | Cell containing an error.
// inlineStr (Inline String) | Cell containing an (inline) rich string, i.e.,
// | one not in the shared string table. If this
// | cell type is used, then the cell value is in
// | the is element rather than the v element in
// | the cell (c element).
// n (Number) | Cell containing a number.
// s (Shared String) | Cell containing a shared string.
// str (String) | Cell containing a formula string.
type xlsxC struct {
XMLName xml.Name `xml:"c"`
XMLSpace xml.Attr `xml:"space,attr,omitempty"`
R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1
S int `xml:"s,attr,omitempty"` // Style reference
T string `xml:"t,attr,omitempty"` // Type
Cm *uint `xml:"cm,attr"`
Vm *uint `xml:"vm,attr"`
Ph *bool `xml:"ph,attr"`
F *xlsxF `xml:"f"` // Formula
V string `xml:"v,omitempty"` // Value
IS *xlsxSI `xml:"is"`
f string
}
// xlsxF represents a formula for the cell. The formula expression is
// contained in the character node of this element.
type xlsxF struct {
Content string `xml:",chardata"`
T string `xml:"t,attr,omitempty"` // Formula type
Aca bool `xml:"aca,attr,omitempty"`
Ref string `xml:"ref,attr,omitempty"` // Shared formula ref
Dt2D bool `xml:"dt2D,attr,omitempty"`
Dtr bool `xml:"dtr,attr,omitempty"`
Del1 bool `xml:"del1,attr,omitempty"`
Del2 bool `xml:"del2,attr,omitempty"`
R1 string `xml:"r1,attr,omitempty"`
R2 string `xml:"r2,attr,omitempty"`
Ca bool `xml:"ca,attr,omitempty"`
Si *int `xml:"si,attr"` // Shared formula index
Bx bool `xml:"bx,attr,omitempty"`
}
// xlsxSheetProtection collection expresses the sheet protection options to
// enforce when the sheet is protected.
type xlsxSheetProtection struct {
XMLName xml.Name `xml:"sheetProtection"`
AlgorithmName string `xml:"algorithmName,attr,omitempty"`
Password string `xml:"password,attr,omitempty"`
HashValue string `xml:"hashValue,attr,omitempty"`
SaltValue string `xml:"saltValue,attr,omitempty"`
SpinCount int `xml:"spinCount,attr,omitempty"`
Sheet bool `xml:"sheet,attr"`
Objects bool `xml:"objects,attr"`
Scenarios bool `xml:"scenarios,attr"`
FormatCells bool `xml:"formatCells,attr"`
FormatColumns bool `xml:"formatColumns,attr"`
FormatRows bool `xml:"formatRows,attr"`
InsertColumns bool `xml:"insertColumns,attr"`
InsertRows bool `xml:"insertRows,attr"`
InsertHyperlinks bool `xml:"insertHyperlinks,attr"`
DeleteColumns bool `xml:"deleteColumns,attr"`
DeleteRows bool `xml:"deleteRows,attr"`
SelectLockedCells bool `xml:"selectLockedCells,attr"`
Sort bool `xml:"sort,attr"`
AutoFilter bool `xml:"autoFilter,attr"`
PivotTables bool `xml:"pivotTables,attr"`
SelectUnlockedCells bool `xml:"selectUnlockedCells,attr"`
}
// xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic
// properties that affect the display of phonetic text for this String Item
// (si). Phonetic text is used to give hints as to the pronunciation of an East
// Asian language, and the hints are displayed as text within the spreadsheet
// cells across the top portion of the cell. Since the phonetic hints are text,
// every phonetic hint is expressed as a phonetic run (rPh), and these
// properties specify how to display that phonetic run.
type xlsxPhoneticPr struct {
XMLName xml.Name `xml:"phoneticPr"`
Alignment string `xml:"alignment,attr,omitempty"`
FontID *int `xml:"fontId,attr"`
Type string `xml:"type,attr,omitempty"`
}
// A Conditional Format is a format, such as cell shading or font color, that a
// spreadsheet application can automatically apply to cells if a specified
// condition is true. This collection expresses conditional formatting rules
// applied to a particular cell or range.
type xlsxConditionalFormatting struct {
XMLName xml.Name `xml:"conditionalFormatting"`
Pivot bool `xml:"pivot,attr,omitempty"`
SQRef string `xml:"sqref,attr,omitempty"`
CfRule []*xlsxCfRule `xml:"cfRule"`
}
// xlsxCfRule (Conditional Formatting Rule) represents a description of a
// conditional formatting rule.
type xlsxCfRule struct {
Type string `xml:"type,attr,omitempty"`
DxfID *int `xml:"dxfId,attr"`
Priority int `xml:"priority,attr,omitempty"`
StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"`
AboveAverage *bool `xml:"aboveAverage,attr"`
Percent bool `xml:"percent,attr,omitempty"`
Bottom bool `xml:"bottom,attr,omitempty"`
Operator string `xml:"operator,attr,omitempty"`
Text string `xml:"text,attr,omitempty"`
TimePeriod string `xml:"timePeriod,attr,omitempty"`
Rank int `xml:"rank,attr,omitempty"`
StdDev int `xml:"stdDev,attr,omitempty"`
EqualAverage bool `xml:"equalAverage,attr,omitempty"`
Formula []string `xml:"formula,omitempty"`
ColorScale *xlsxColorScale `xml:"colorScale"`
DataBar *xlsxDataBar `xml:"dataBar"`
IconSet *xlsxIconSet `xml:"iconSet"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxColorScale (Color Scale) describes a gradated color scale in this
// conditional formatting rule.
type xlsxColorScale struct {
Cfvo []*xlsxCfvo `xml:"cfvo"`
Color []*xlsxColor `xml:"color"`
}
// dataBar (Data Bar) describes a data bar conditional formatting rule.
type xlsxDataBar struct {
MaxLength int `xml:"maxLength,attr,omitempty"`
MinLength int `xml:"minLength,attr,omitempty"`
ShowValue *bool `xml:"showValue,attr"`
Cfvo []*xlsxCfvo `xml:"cfvo"`
Color []*xlsxColor `xml:"color"`
}
// xlsxIconSet (Icon Set) describes an icon set conditional formatting rule.
type xlsxIconSet struct {
Cfvo []*xlsxCfvo `xml:"cfvo"`
IconSet string `xml:"iconSet,attr,omitempty"`
ShowValue *bool `xml:"showValue,attr"`
Percent bool `xml:"percent,attr,omitempty"`
Reverse bool `xml:"reverse,attr,omitempty"`
}
// cfvo (Conditional Format Value Object) describes the values of the
// interpolation points in a gradient scale.
type xlsxCfvo struct {
Gte bool `xml:"gte,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Val string `xml:"val,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxHyperlinks directly maps the hyperlinks element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - A hyperlink can
// be stored in a package as a relationship. Hyperlinks shall be identified by
// containing a target which specifies the destination of the given hyperlink.
type xlsxHyperlinks struct {
XMLName xml.Name `xml:"hyperlinks"`
Hyperlink []xlsxHyperlink `xml:"hyperlink"`
}
// xlsxHyperlink directly maps the hyperlink element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main
type xlsxHyperlink struct {
Ref string `xml:"ref,attr"`
Location string `xml:"location,attr,omitempty"`
Display string `xml:"display,attr,omitempty"`
Tooltip string `xml:"tooltip,attr,omitempty"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxTableParts directly maps the tableParts element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - The table element
// has several attributes applied to identify the table and the data range it
// covers. The table id attribute needs to be unique across all table parts, the
// same goes for the name and displayName. The displayName has the further
// restriction that it must be unique across all defined names in the workbook.
// Later on we will see that you can define names for many elements, such as
// cells or formulas. The name value is used for the object model in Microsoft
// Office Excel. The displayName is used for references in formulas. The ref
// attribute is used to identify the cell range that the table covers. This
// includes not only the table data, but also the table header containing column
// names.
// To add columns to your table you add new tableColumn elements to the
// tableColumns container. Similar to the shared string table the collection
// keeps a count attribute identifying the number of columns. Besides the table
// definition in the table part there is also the need to identify which tables
// are displayed in the worksheet. The worksheet part has a separate element
// tableParts to store this information. Each table part is referenced through
// the relationship ID and again a count of the number of table parts is
// maintained. The following markup sample is taken from the documents
// accompanying this book. The sheet data element has been removed to reduce the
// size of the sample. To reference the table, just add the tableParts element,
// of course after having created and stored the table part. For example:
//
//
// ...
//
//
//
//
type xlsxTableParts struct {
XMLName xml.Name `xml:"tableParts"`
Count int `xml:"count,attr,omitempty"`
TableParts []*xlsxTablePart `xml:"tablePart"`
}
// xlsxTablePart directly maps the tablePart element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main
type xlsxTablePart struct {
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxPicture directly maps the picture element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Background sheet
// image. For example:
//
//
type xlsxPicture struct {
XMLName xml.Name `xml:"picture"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxIgnoredError specifies a single ignored error for a range of cells.
type xlsxIgnoredError struct {
XMLName xml.Name `xml:"ignoredError"`
Sqref string `xml:"sqref,attr"`
EvalError bool `xml:"evalError,attr,omitempty"`
TwoDigitTextYear bool `xml:"twoDigitTextYear,attr,omitempty"`
NumberStoredAsText bool `xml:"numberStoredAsText,attr,omitempty"`
Formula bool `xml:"formula,attr,omitempty"`
FormulaRange bool `xml:"formulaRange,attr,omitempty"`
UnlockedFormula bool `xml:"unlockedFormula,attr,omitempty"`
EmptyCellReference bool `xml:"emptyCellReference,attr,omitempty"`
ListDataValidation bool `xml:"listDataValidation,attr,omitempty"`
CalculatedColumn bool `xml:"calculatedColumn,attr,omitempty"`
}
// xlsxIgnoredErrors specifies a collection of ignored errors, by cell range.
type xlsxIgnoredErrors struct {
XMLName xml.Name `xml:"ignoredErrors"`
IgnoredError []xlsxIgnoredError `xml:"ignoredError"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxLegacyDrawing directly maps the legacyDrawing element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - A comment is a
// rich text note that is attached to, and associated with, a cell, separate
// from other cell content. Comment content is stored separate from the cell,
// and is displayed in a drawing object (like a text box) that is separate from,
// but associated with, a cell. Comments are used as reminders, such as noting
// how a complex formula works, or to provide feedback to other users. Comments
// can also be used to explain assumptions made in a formula or to call out
// something special about the cell.
type xlsxLegacyDrawing struct {
XMLName xml.Name `xml:"legacyDrawing"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// xlsxLegacyDrawingHF specifies the explicit relationship to the part
// containing the VML defining pictures rendered in the header / footer of the
// sheet.
type xlsxLegacyDrawingHF struct {
XMLName xml.Name `xml:"legacyDrawingHF"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
}
// decodeX14SparklineGroups directly maps the sparklineGroups element.
type decodeX14SparklineGroups struct {
XMLName xml.Name `xml:"sparklineGroups"`
XMLNSXM string `xml:"xmlns:xm,attr"`
Content string `xml:",innerxml"`
}
// decodeX14ConditionalFormattingExt directly maps the ext element.
type decodeX14ConditionalFormattingExt struct {
XMLName xml.Name `xml:"ext"`
ID string `xml:"id"`
}
// decodeX14ConditionalFormattings directly maps the conditionalFormattings
// element.
type decodeX14ConditionalFormattings struct {
XMLName xml.Name `xml:"conditionalFormattings"`
XMLNSXM string `xml:"xmlns:xm,attr"`
Content string `xml:",innerxml"`
}
// decodeX14ConditionalFormattingRules directly maps the conditionalFormattings
// element.
type decodeX14ConditionalFormattingRules struct {
XMLName xml.Name `xml:"conditionalFormattings"`
XMLNSXM string `xml:"xmlns:xm,attr"`
CondFmt []decodeX14ConditionalFormatting `xml:"conditionalFormatting"`
}
// decodeX14ConditionalFormatting directly maps the conditionalFormatting
// element.
type decodeX14ConditionalFormatting struct {
XMLName xml.Name `xml:"conditionalFormatting"`
Pivot bool `xml:"pivot,attr,omitempty"`
CfRule []*decodeX14CfRule `xml:"cfRule"`
Sqref string `xml:"sqref,omitempty"`
ExtLst *xlsxExtLst `xml:"x14:extLst"`
}
// decodeX14CfRule directly maps the cfRule element.
type decodeX14CfRule struct {
XMLName xml.Name `xml:"cfRule"`
Type string `xml:"type,attr,omitempty"`
Priority int `xml:"priority,attr,omitempty"`
StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"`
AboveAverage *bool `xml:"aboveAverage,attr"`
Percent bool `xml:"percent,attr,omitempty"`
Bottom bool `xml:"bottom,attr,omitempty"`
Operator string `xml:"operator,attr,omitempty"`
Text string `xml:"text,attr,omitempty"`
TimePeriod string `xml:"timePeriod,attr,omitempty"`
Rank int `xml:"rank,attr,omitempty"`
StdDev int `xml:"stdDev,attr,omitempty"`
EqualAverage bool `xml:"equalAverage,attr,omitempty"`
ActivePresent bool `xml:"activePresent,attr,omitempty"`
ID string `xml:"id,attr,omitempty"`
F []string `xml:"f"`
ColorScale *xlsxInnerXML `xml:"colorScale"`
DataBar *decodeX14DataBar `xml:"dataBar"`
IconSet *decodeX14IconSet `xml:"iconSet"`
Dxf *xlsxInnerXML `xml:"dxf"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// decodeX14DataBar directly maps the dataBar element.
type decodeX14DataBar struct {
XMLName xml.Name `xml:"dataBar"`
MaxLength int `xml:"maxLength,attr"`
MinLength int `xml:"minLength,attr"`
Border bool `xml:"border,attr,omitempty"`
Gradient *bool `xml:"gradient,attr"`
ShowValue bool `xml:"showValue,attr,omitempty"`
Direction string `xml:"direction,attr,omitempty"`
Cfvo []*xlsxCfvo `xml:"cfvo"`
BorderColor *xlsxColor `xml:"borderColor"`
NegativeFillColor *xlsxColor `xml:"negativeFillColor"`
AxisColor *xlsxColor `xml:"axisColor"`
}
// decodeX14IconSet directly maps the iconSet element.
type decodeX14IconSet struct {
XMLName xml.Name `xml:"iconSet"`
IconSet string `xml:"iconSet,attr,omitempty"`
ShowValue *bool `xml:"showValue,attr"`
Percent *bool `xml:"percent,attr"`
Reverse bool `xml:"reverse,attr,omitempty"`
Custom bool `xml:"custom,attr,omitempty"`
Cfvo []*decodeX14Cfvo `xml:"cfvo"`
CfIcon []*xlsxInnerXML `xml:"cfIcon"`
}
// decodeX14Cfvo directly maps the cfvo element.
type decodeX14Cfvo struct {
XMLName xml.Name `xml:"cfvo"`
Type string `xml:"type,attr"`
Gte *bool `xml:"gte,attr"`
F string `xml:"f"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxX14ConditionalFormattings directly maps the x14:conditionalFormattings
// element.
type xlsxX14ConditionalFormattings struct {
XMLName xml.Name `xml:"x14:conditionalFormattings"`
Content string `xml:",innerxml"`
}
// xlsxX14ConditionalFormatting directly maps the x14:conditionalFormatting
// element.
type xlsxX14ConditionalFormatting struct {
XMLName xml.Name `xml:"x14:conditionalFormatting"`
XMLNSXM string `xml:"xmlns:xm,attr"`
Pivot bool `xml:"pivot,attr,omitempty"`
CfRule []*xlsxX14CfRule `xml:"x14:cfRule"`
Sqref string `xml:"xm:sqref,omitempty"`
ExtLst *xlsxExtLst `xml:"x14:extLst"`
}
// xlsxX14CfRule directly maps the x14:cfRule element.
type xlsxX14CfRule struct {
Type string `xml:"type,attr,omitempty"`
Priority int `xml:"priority,attr,omitempty"`
StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"`
AboveAverage *bool `xml:"aboveAverage,attr"`
Percent bool `xml:"percent,attr,omitempty"`
Bottom bool `xml:"bottom,attr,omitempty"`
Operator string `xml:"operator,attr,omitempty"`
Text string `xml:"text,attr,omitempty"`
TimePeriod string `xml:"timePeriod,attr,omitempty"`
Rank int `xml:"rank,attr,omitempty"`
StdDev int `xml:"stdDev,attr,omitempty"`
EqualAverage bool `xml:"equalAverage,attr,omitempty"`
ActivePresent bool `xml:"activePresent,attr,omitempty"`
ID string `xml:"id,attr,omitempty"`
F []string `xml:"xm:f"`
ColorScale *xlsxInnerXML `xml:"x14:colorScale"`
DataBar *xlsx14DataBar `xml:"x14:dataBar"`
IconSet *xlsx14IconSet `xml:"x14:iconSet"`
Dxf *xlsxInnerXML `xml:"x14:dxf"`
ExtLst *xlsxExtLst `xml:"x14:extLst"`
}
// xlsx14DataBar directly maps the x14:dataBar element.
type xlsx14DataBar struct {
MaxLength int `xml:"maxLength,attr"`
MinLength int `xml:"minLength,attr"`
Border bool `xml:"border,attr"`
Gradient bool `xml:"gradient,attr"`
ShowValue bool `xml:"showValue,attr,omitempty"`
Direction string `xml:"direction,attr,omitempty"`
Cfvo []*xlsxCfvo `xml:"x14:cfvo"`
BorderColor *xlsxColor `xml:"x14:borderColor"`
NegativeFillColor *xlsxColor `xml:"x14:negativeFillColor"`
AxisColor *xlsxColor `xml:"x14:axisColor"`
}
// xlsx14IconSet directly maps the x14:iconSet element. This element specifies
// the properties of a conditional formatting rule that uses an icon set.
type xlsx14IconSet struct {
IconSet string `xml:"iconSet,attr,omitempty"`
ShowValue *bool `xml:"showValue,attr"`
Percent *bool `xml:"percent,attr"`
Reverse bool `xml:"reverse,attr,omitempty"`
Custom bool `xml:"custom,attr,omitempty"`
Cfvo []*xlsx14Cfvo `xml:"x14:cfvo"`
CfIcon []*xlsxInnerXML `xml:"x14:cfIcon"`
}
// xlsx14IconSet directly maps the x14:cfvo element. This element specifies how
// to calculate a value from the range of cells to which a conditional
// formatting rule applies.
type xlsx14Cfvo struct {
Type string `xml:"type,attr"`
Gte *bool `xml:"gte,attr"`
F string `xml:"xm:f"`
ExtLst *xlsxExtLst `xml:"x14:extLst"`
}
// xlsxX14SparklineGroups directly maps the x14:sparklineGroups element.
type xlsxX14SparklineGroups struct {
XMLName xml.Name `xml:"x14:sparklineGroups"`
XMLNSXM string `xml:"xmlns:xm,attr"`
SparklineGroups []*xlsxX14SparklineGroup `xml:"x14:sparklineGroup"`
Content string `xml:",innerxml"`
}
// xlsxX14SparklineGroup directly maps the x14:sparklineGroup element.
type xlsxX14SparklineGroup struct {
XMLName xml.Name `xml:"x14:sparklineGroup"`
ManualMax int `xml:"manualMax,attr,omitempty"`
ManualMin int `xml:"manualMin,attr,omitempty"`
LineWeight float64 `xml:"lineWeight,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
DateAxis bool `xml:"dateAxis,attr,omitempty"`
DisplayEmptyCellsAs string `xml:"displayEmptyCellsAs,attr,omitempty"`
Markers bool `xml:"markers,attr,omitempty"`
High bool `xml:"high,attr,omitempty"`
Low bool `xml:"low,attr,omitempty"`
First bool `xml:"first,attr,omitempty"`
Last bool `xml:"last,attr,omitempty"`
Negative bool `xml:"negative,attr,omitempty"`
DisplayXAxis bool `xml:"displayXAxis,attr,omitempty"`
DisplayHidden bool `xml:"displayHidden,attr,omitempty"`
MinAxisType string `xml:"minAxisType,attr,omitempty"`
MaxAxisType string `xml:"maxAxisType,attr,omitempty"`
RightToLeft bool `xml:"rightToLeft,attr,omitempty"`
ColorSeries *xlsxColor `xml:"x14:colorSeries"`
ColorNegative *xlsxColor `xml:"x14:colorNegative"`
ColorAxis *xlsxColor `xml:"x14:colorAxis"`
ColorMarkers *xlsxColor `xml:"x14:colorMarkers"`
ColorFirst *xlsxColor `xml:"x14:colorFirst"`
ColorLast *xlsxColor `xml:"x14:colorLast"`
ColorHigh *xlsxColor `xml:"x14:colorHigh"`
ColorLow *xlsxColor `xml:"x14:colorLow"`
Sparklines xlsxX14Sparklines `xml:"x14:sparklines"`
}
// xlsxX14Sparklines directly maps the x14:sparklines element.
type xlsxX14Sparklines struct {
Sparkline []*xlsxX14Sparkline `xml:"x14:sparkline"`
}
// xlsxX14Sparkline directly maps the x14:sparkline element.
type xlsxX14Sparkline struct {
F string `xml:"xm:f"`
Sqref string `xml:"xm:sqref"`
}
// DataValidation directly maps the settings of the data validation rule.
type DataValidation struct {
AllowBlank bool
Error *string
ErrorStyle *string
ErrorTitle *string
Operator string
Prompt *string
PromptTitle *string
ShowDropDown bool
ShowErrorMessage bool
ShowInputMessage bool
Sqref string
Type string
Formula1 string
Formula2 string
}
// SparklineOptions directly maps the settings of the sparkline.
type SparklineOptions struct {
Location []string
Range []string
Max int
CustMax int
Min int
CustMin int
Type string
Weight float64
DateAxis bool
Markers bool
High bool
Low bool
First bool
Last bool
Negative bool
Axis bool
Hidden bool
Reverse bool
Style int
SeriesColor string
NegativeColor string
MarkersColor string
FirstColor string
LastColor string
HightColor string
LowColor string
EmptyCells string
}
// Selection directly maps the settings of the worksheet selection.
type Selection struct {
SQRef string
ActiveCell string
Pane string
}
// Panes directly maps the settings of the panes.
type Panes struct {
Freeze bool
Split bool
XSplit int
YSplit int
TopLeftCell string
ActivePane string
Selection []Selection
}
// ConditionalFormatOptions directly maps the conditional format settings of the cells.
type ConditionalFormatOptions struct {
Type string
AboveAverage bool
Percent bool
Format *int
Criteria string
Value string
MinType string
MidType string
MaxType string
MinValue string
MidValue string
MaxValue string
MinColor string
MidColor string
MaxColor string
BarColor string
BarBorderColor string
BarDirection string
BarOnly bool
BarSolid bool
IconStyle string
ReverseIcons bool
IconsOnly bool
StopIfTrue bool
}
// SheetProtectionOptions directly maps the settings of worksheet protection.
type SheetProtectionOptions struct {
AlgorithmName string
AutoFilter bool
DeleteColumns bool
DeleteRows bool
EditObjects bool
EditScenarios bool
FormatCells bool
FormatColumns bool
FormatRows bool
InsertColumns bool
InsertHyperlinks bool
InsertRows bool
Password string
PivotTables bool
SelectLockedCells bool
SelectUnlockedCells bool
Sort bool
}
// HeaderFooterOptions directly maps the settings of header and footer.
type HeaderFooterOptions struct {
AlignWithMargins *bool
DifferentFirst bool
DifferentOddEven bool
ScaleWithDoc *bool
OddHeader string
OddFooter string
EvenHeader string
EvenFooter string
FirstHeader string
FirstFooter string
}
// PageLayoutMarginsOptions directly maps the settings of page layout margins.
type PageLayoutMarginsOptions struct {
Bottom *float64
Footer *float64
Header *float64
Left *float64
Right *float64
Top *float64
Horizontally *bool
Vertically *bool
}
// PageLayoutOptions directly maps the settings of page layout.
type PageLayoutOptions struct {
// Size defines the paper size of the worksheet.
Size *int
// Orientation defines the orientation of page layout for a worksheet.
Orientation *string
// FirstPageNumber specified the first printed page number. If no value is
// specified, then 'automatic' is assumed.
FirstPageNumber *uint
// AdjustTo defines the print scaling. This attribute is restricted to
// value ranging from 10 (10%) to 400 (400%). This setting is overridden
// when fitToWidth and/or fitToHeight are in use.
AdjustTo *uint
// FitToHeight specified the number of vertical pages to fit on.
FitToHeight *int
// FitToWidth specified the number of horizontal pages to fit on.
FitToWidth *int
// BlackAndWhite specified print black and white.
BlackAndWhite *bool
// PageOrder specifies the ordering of multiple pages. Values
// accepted: overThenDown and downThenOver
PageOrder *string
}
// ViewOptions directly maps the settings of sheet view.
type ViewOptions struct {
// DefaultGridColor indicating that the consuming application should use
// the default grid lines color(system dependent). Overrides any color
// specified in colorId.
DefaultGridColor *bool
// RightToLeft indicating whether the sheet is in 'right to left' display
// mode. When in this mode, Column A is on the far right, Column B; is one
// column left of Column A, and so on. Also, information in cells is
// displayed in the Right to Left format.
RightToLeft *bool
// ShowFormulas indicating whether this sheet should display formulas.
ShowFormulas *bool
// ShowGridLines indicating whether this sheet should display grid lines.
ShowGridLines *bool
// ShowRowColHeaders indicating whether the sheet should display row and
// column headings.
ShowRowColHeaders *bool
// ShowRuler indicating this sheet should display ruler.
ShowRuler *bool
// ShowZeros indicating whether to "show a zero in cells that have zero
// value". When using a formula to reference another cell which is empty,
// the referenced value becomes 0 when the flag is true. (Default setting
// is true.)
ShowZeros *bool
// TopLeftCell specifies a location of the top left visible cell Location
// of the top left visible cell in the bottom right pane (when in
// Left-to-Right mode).
TopLeftCell *string
// View indicating how sheet is displayed, by default it uses empty string
// available options: normal, pageLayout, pageBreakPreview
View *string
// ZoomScale specifies a window zoom magnification for current view
// representing percent values. This attribute is restricted to values
// ranging from 10 to 400. Horizontal & Vertical scale together.
ZoomScale *float64
}
// SheetPropsOptions provides a function to set worksheet properties. There 4
// kinds of presets "Custom Scaling Options" in the spreadsheet applications, if
// you need to set those kind of scaling options, please using the
// "SetSheetProps" and "SetPageLayout" functions to approach these 4 scaling
// options:
//
// 1. No Scaling (Print sheets at their actual size):
//
// disable := false
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// FitToPage: &disable,
// }); err != nil {
// fmt.Println(err)
// }
//
// 2. Fit Sheet on One Page (Shrink the printout so that it fits on one page):
//
// enable := true
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// FitToPage: &enable,
// }); err != nil {
// fmt.Println(err)
// }
//
// 3. Fit All Columns on One Page (Shrink the printout so that it is one page
// wide):
//
// enable, zero := true, 0
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// FitToPage: &enable,
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SetPageLayout("Sheet1", &excelize.PageLayoutOptions{
// FitToHeight: &zero,
// }); err != nil {
// fmt.Println(err)
// }
//
// 4. Fit All Rows on One Page (Shrink the printout so that it is one page
// high):
//
// enable, zero := true, 0
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// FitToPage: &enable,
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SetPageLayout("Sheet1", &excelize.PageLayoutOptions{
// FitToWidth: &zero,
// }); err != nil {
// fmt.Println(err)
// }
type SheetPropsOptions struct {
// Specifies a stable name of the sheet, which should not change over time,
// and does not change from user input. This name should be used by code
// to reference a particular sheet.
CodeName *string
// EnableFormatConditionsCalculation indicating whether the conditional
// formatting calculations shall be evaluated. If set to false, then the
// min/max values of color scales or data bars or threshold values in Top N
// rules shall not be updated. Essentially the conditional
// formatting "calc" is off.
EnableFormatConditionsCalculation *bool
// Published indicating whether the worksheet is published.
Published *bool
// AutoPageBreaks indicating whether the sheet displays Automatic Page
// Breaks.
AutoPageBreaks *bool
// FitToPage indicating whether the Fit to Page print option is enabled.
FitToPage *bool
// TabColorIndexed represents the indexed color value.
TabColorIndexed *int
// TabColorRGB represents the standard Alpha Red Green Blue color value.
TabColorRGB *string
// TabColorTheme represents the zero-based index into the collection,
// referencing a particular value expressed in the Theme part.
TabColorTheme *int
// TabColorTint specifies the tint value applied to the color.
TabColorTint *float64
// OutlineSummaryBelow indicating whether summary rows appear below detail
// in an outline, when applying an outline.
OutlineSummaryBelow *bool
// OutlineSummaryRight indicating whether summary columns appear to the
// right of detail in an outline, when applying an outline.
OutlineSummaryRight *bool
// BaseColWidth specifies the number of characters of the maximum digit
// width of the normal style's font. This value does not include margin
// padding or extra padding for grid lines. It is only the number of
// characters.
BaseColWidth *uint8
// DefaultColWidth specifies the default column width measured as the
// number of characters of the maximum digit width of the normal style's
// font.
DefaultColWidth *float64
// DefaultRowHeight specifies the default row height measured in point
// size. Optimization so we don't have to write the height on all rows.
// This can be written out if most rows have custom height, to achieve the
// optimization.
DefaultRowHeight *float64
// CustomHeight specifies the custom height.
CustomHeight *bool
// ZeroHeight specifies if rows are hidden.
ZeroHeight *bool
// ThickTop specifies if rows have a thick top border by default.
ThickTop *bool
// ThickBottom specifies if rows have a thick bottom border by default.
ThickBottom *bool
}